具有通用返回类型的高阶函数的类型推断

时间:2019-02-21 04:10:11

标签: kotlin

以下示例在Kotlin 1.3.21中是完全合法的:

fun <T> foo(bar: T): T = bar

val t: Int = foo(1) // No need to declare foo<Int>(1) explicitly

但是为什么类型推断不能用于高阶函数呢?

fun <T> foo() = fun(bar: T): T = bar

val t: Int = foo()(1) // Compile error: Type inference failed...

使用高阶函数时,Kotlin会强制呼叫站点为:

val t = foo<Int>()(1)

即使显式指定了返回类型foo,类型推断仍然失败:

fun <T> foo(): (T) - > T = fun(bar: T): T = bar

val t: Int = foo()(1) // Compile error: Type inference failed...

但是,当泛型类型参数与外部函数共享时,它将起作用!

fun <T> foo(baz: T) = fun (bar: T): T = bar

val t: Int = foo(1)(1) // Horray! But I want to write foo()(1) instead...

如何编写函数foo,以便foo()(1)可以被编译,而bar是通用类型?

3 个答案:

答案 0 :(得分:6)

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.

因此,据我了解:

foo()<-在此处使用类型信息

foo()(1)<-在此处提供信息

看起来类型推断无法“向后”

    val foo = foo<Int>()//create function
    val bar = foo(1)//call function

答案 1 :(得分:3)

简而言之,当您调用动态生成的函数(例如高阶函数的返回值)时,它实际上不是函数调用,它只是{ {1}}功能。

在语法级别,Kotlin像返回函数一样对待invoke() -> A之类的对象,就像它们是普通函数一样-它允许您通过在括号中附加参数来调用它们。这就是为什么您可以(A, B) -> C-foo<Int>()(1)返回类型为foo<Int>()的对象,然后使用(Int) -> (Int)作为参数来调用它的原因。

但是,实际上,这些“功能对象”并不是真正的功能,它们只是带有1运算符方法的简单对象。因此,例如,带有1个参数并返回值的函数对象实际上只是特殊接口invoke的实例,看起来像这样

Function1

任何带有interface Function1<A, R> { operator fun invoke(a: A): R } 的类都可以像函数一样被调用,即您可以只调用operator fun invoke来代替foo.invoke(bar, baz)。 Kotlin有几个内置类,例如foo(bar, baz)FunctionFunction1Function2等,用于表示函数对象。因此,当您呼叫Function<number of args>时,您实际呼叫的是foo<Int>()(1)。您可以通过反编译字节码来确认这一点。

Decompiled Kotlin bytecode

那么这与类型推断有什么关系?好吧,当您调用foo<Int>().invoke(1)时,实际上是在使用一点语法糖来调用foo()(1),这使我们更容易理解为什么推理失败。点运算符的右手侧不能用于推断左手侧的类型,因为必须先评估左手侧。因此,foo().invoke(1)的类型必须明确地声明为foo

答案 2 :(得分:0)

只是玩弄了一下,并分享了一些想法,基本上回答了最后一个问题:“我如何编写函数foo以便编译foo()(1),其中bar是泛型?”:

一个简单的解决方法,但是您放弃了更高阶的功能(或者需要包装它)是要有一个中间对象,例如:

object FooOp {
  operator fun <T> invoke(t : T) = t
}

具有类似以下内容的foo方法:

fun foo() = FooOp

当然不是完全一样,因为您基本上是在解决第一个泛型函数。它基本上与只有1个函数返回我们想要的类型几乎相同,因此也可以再次推断该类型。

替代您的问题的方法可能是以下几种。只需添加另一个实际上指定类型的函数即可:

fun <T> foo() = fun(bar: T): T = bar
@JvmName("fooInt")
fun foo() = fun(bar : Int) = bar

随后两个将成功:

val t: Int = foo()(1)
val t2: String = foo<String>()("...")

但是...(除了可能需要大量的重载之外),不可能定义类似于以下内容的另一个函数:

@JvmName("fooString")
fun foo() = fun(bar : String) = bar

如果定义该函数,它将给您带来类似于以下错误:

Conflicting overloads: @JvmName public final fun foo(): (Int) -> Int defined in XXX, @JvmName public final fun foo(): (String) -> String defined in XXX

但是也许您可以用它来构建一些东西?

否则,我对答案的归纳没有答案。