Type inference for higher order functions with generic return types
I am not an expert on how type inference works, but the basic rule is: At the point of use the compiler must know all types in the expression being used.
So from my understanding is that:
foo() <- using type information here
foo()(1) <- providing the information here
Looks like type inference doesn't work 'backward'
val foo = foo<Int>()//create function
val bar = foo(1)//call function
To put it in simple (possibly over-simplified) terms, when you call a dynamically generated function, such as the return value of a higher-order function, it's not actually a function call, it's just syntactic sugar for the invoke
function.
At the syntax level, Kotlin treats objects with return types like () -> A
and (A, B) -> C
like they are normal functions - it allows you to call them by just attaching arguments in parenthesis. This is why you can do foo<Int>()(1)
- foo<Int>()
returns an object of type (Int) -> (Int)
, which is then called with 1
as an argument.
However, under the hood, these "function objects" aren't really functions, they are just plain objects with an invoke
operator method. So for example, function objects that take 1 argument and return a value are really just instances of the special interface Function1
which looks something like this
interface Function1<A, R> {
operator fun invoke(a: A): R
}
Any class with operator fun invoke
can be called like a function i.e. instead of foo.invoke(bar, baz)
you can just call foo(bar, baz)
. Kotlin has several built-in classes like this named Function
, Function1
, Function2
, Function<number of args>
etc. used to represent function objects. So when you call foo<Int>()(1)
, what you are actually calling is foo<Int>().invoke(1)
. You can confirm this by decompiling the bytecode.
So what does this have to do with type inference? Well when you call foo()(1)
, you are actually calling foo().invoke(1)
with a little syntactic sugar, which makes it a bit easier to see why inference fails. The right hand side of the dot operator cannot be used to infer types for the left hand side, because the left hand side has to be evaluated first. So the type for foo
has to be explicitly stated as foo<Int>
.