代码之家  ›  专栏  ›  技术社区  ›  breandan

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

  •  0
  • breandan  · 技术社区  · 7 年前

    以下示例在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()(1) 将编译,在哪里 bar 是泛型吗?

    0 回复  |  直到 6 年前
        1
  •  6
  •   PolishCivil    7 年前

    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
    
        2
  •  3
  •   Leo Aso    7 年前

    简单地说(可能过于简化),当你调用一个动态生成的函数,比如一个高阶函数的返回值时,它实际上不是一个函数调用,它只是 invoke 作用

    在语法级别,Kotlin处理返回类型为 () -> A 和 (A, B) -> C 就像它们是普通函数一样,它允许您通过在括号中附加参数来调用它们。这就是你能做到的原因 foo<Int>()(1) - foo<Int>() 返回类型为的对象 (Int) -> (Int) ,然后用 1 作为论据。

    然而,在引擎盖下,这些“功能对象”并不是真正的功能,它们只是带有 援引 运算符方法。例如,接受1个参数并返回值的函数对象实际上只是特殊接口的实例 Function1 看起来像这样

    interface Function1<A, R> {
        operator fun invoke(a: A): R
    }
    

    有什么课吗 operator fun invoke 可以像函数一样调用,即代替 foo.invoke(bar, baz) 你可以打电话 foo(bar, baz) .Kotlin有几个像这样的内置类 Function , 功能1 , Function2 , Function<number of args> 等,用于表示函数对象。所以当你打电话的时候 富<Int>()(1) ,你实际上是在说 foo<Int>().invoke(1) .你可以通过反编译字节码来确认这一点。

    Decompiled Kotlin bytecode

    那么这和类型推断有什么关系呢?当你打电话的时候 foo()(1) 你真的在打电话吗 foo().invoke(1) 加上一点语法糖分,这就更容易理解推理失败的原因。点运算符的右侧不能用于推断左侧的类型,因为必须先计算左侧。那么,这是什么类型 foo 必须明确表示为 foo<Int> .

        3
  •  0
  •   Roland    7 年前

    只是简单地玩了一下,分享了一些想法,基本上回答了最后一个问题“我该如何编写函数?” foo 因此 foo()(1) 将编译,在哪里 bar 是泛型吗?":

    一个简单的解决方法是在适当的位置放置一个中间对象,例如:

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

    用一个 福 -方法如下:

    fun foo() = FooOp
    

    当然,这并不完全相同,因为基本上是围绕第一个泛型函数工作的。它基本上与只有一个函数返回我们想要的类型几乎相同,因此它也能够再次推断类型。

    你的问题的另一个选择可能是:。只需添加另一个实际指定类型的函数:

    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
    

    但也许你能用它来构建一些东西?

    否则,我无法回答为什么会推断出它,为什么不会。