以下示例在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
是通用类型?
答案 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)
,Function
,Function1
,Function2
等,用于表示函数对象。因此,当您呼叫Function<number of args>
时,您实际呼叫的是foo<Int>()(1)
。您可以通过反编译字节码来确认这一点。
那么这与类型推断有什么关系?好吧,当您调用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
但是也许您可以用它来构建一些东西?
否则,我对答案的归纳没有答案。