It'd be nice to have a way to have a default type ...
# language-evolution
c
It'd be nice to have a way to have a default type parameter when the compiler doesn't know how to infer it.
Copy code
interface Foo<In> {

    fun <Out> foo(block: FooDsl<In, Out>.() -> Unit) { … }
}

class FooDsl<In, Out> {
    
    fun one(a: In)

    fun two(a: In, b: Out)

}

val f = object : Foo<String> { … }
The compiler knows how to infer this one:
Copy code
f.foo {
    two("a", 5)
}
because the only missing piece is
Out
and it's provided. However, the compiler doesn't know how to infer this one:
Copy code
f.foo {
    one("a")
}
because
Out
never appears. However, as the library author, I have knowledge in this situation that what the user intended is for
In === Out
, which the user can verbosely write as:
Copy code
f.foo<String> {
    one("a")
}
I'd be great if I could somehow hint this to the compiler.
➕ 3
The traditional way would be to have an overload for the simple case, but in this situation I don't think I can get that to work since the two cases are way to similar
y
AFK, but I wonder if extension functions could do the trick here. Have 2
one
extensions with the
In == Out
version having higher priority. No clue if it works, but worth a shot
Seems to work:
Copy code
interface Foo<In> {
  fun <Out> foo(block: FooDsl<In, Out>.() -> Unit) { }
}
class FooDsl<In, Out> {
  fun two(a: In, b: Out) {}
}

fun <In> FooDsl<In, In>.one(a: In) {}
fun <In, Out> FooDsl<In, Out>.one(a: In, unit: Unit = Unit) {}
 
val f = object: Foo<String> {}

fun main() {
  f.foo {
    two("a", 5)
  }
  f.foo {
    one("a")
  }
  f.foo {
    two("a", 5)
    one("a")
  }
}
It does result in this behavior:
Copy code
f.foo {
    one("a")
    two("a", 5) // Argument type mismatch: actual type is 'Int', but 'String' was expected.
  }
  f.foo {
    two("a", 5)
    one("a")
  }
which you might not want, but I think it makes sense here.
a
The problem in general is defining "the compiler doesn't know". The simple case -- there's just no type constraint on that type variable -- very rarely happens. So in practice you end up with some heuristics that make typing too unpredictable (at least for my taste)
c
In my case, both type variables are bound by
Any
Actually I can cheat 🤔
Sorry, I found a bypass of the entire problem
In
is bound into the wrapper type
Foo
. It's only
Out
that needs to be specified, in the case of
one(a: In)
. But since in the case where we don't know
Out
I want to infer it as
In
… then I can just declare
one(a: Out)
, no?
Hm, that's not enough because there's also a
exclude()
which has no argument at all
If possible I'd prefer avoiding extension functions because they need explicit import
y
I believe the approach I had above should work for
exclude
as well. There's the order-dependence I highlighted, but I think that's fine. There isn't too much you can do to avoid extension functions here. Well, you can do something like this:
Copy code
object FooDslExt {
  context(dsl: FooDsl<In, In>)
  fun <In> exclude() {}
}
interface Foo<In> {
  fun <Out> foo(block: context(FooDsl<In, Out>) FooDslExt.() -> Unit)
}
Because builder inference works on contexts
c
Oh, interesting to use the context for this! 👀