【问题标题】:Type inference for higher order functions with generic return types具有通用返回类型的高阶函数的类型推断
【发布时间】:2019-10-25 17:28:20
【问题描述】:

以下示例在 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 是泛型类型?

【问题讨论】:

    标签: kotlin


    【解决方案1】:

    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
    

    【讨论】:

    • 这是有道理的,但是为什么fun &lt;T&gt; foo(baz: T) = fun (bar: T): T = bar 有效呢?
    • 因为您通过 baz 参数提供类型。所以 val fooRet = foo(1)
    • 我想使用默认参数作为解决方法,但不幸的是这不起作用:fun &lt;T&gt; foo(baz: T? = null) = fun (bar: T): T = bar(您仍然需要在调用站点指定foo&lt;Int&gt;()(1))。
    • 默认参数还需要一些值/类型定义,需要知道'null'是什么类型
    【解决方案2】:

    简单来说(可能过于简单化),当你调用一个动态生成的函数时,比如高阶函数的返回值,它实际上并不是一个函数调用,它只是@的语法糖987654323@函数。

    在语法级别,Kotlin 将返回类型如 () -&gt; A(A, B) -&gt; C 的对象视为普通函数 - 它允许您通过在括号中附加参数来调用它们。这就是为什么你可以这样做 foo&lt;Int&gt;()(1) - foo&lt;Int&gt;() 返回一个 (Int) -&gt; (Int) 类型的对象,然后使用 1 作为参数调用它。

    然而,在底层,这些“函数对象”并不是真正的函数,它们只是带有invoke 运算符方法的普通对象。例如,接受 1 个参数并返回一个值的函数对象实际上只是特殊接口 Function1 的实例,它看起来像这样

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

    任何带有operator fun invoke 的类都可以像函数一样被调用,即你可以调用foo(bar, baz) 而不是foo.invoke(bar, baz)。 Kotlin 有几个像这样的内置类,名为FunctionFunction1Function2Function&lt;number of args&gt; 等,用于表示函数对象。因此,当您调用foo&lt;Int&gt;()(1) 时,您实际调用的是foo&lt;Int&gt;().invoke(1)。您可以通过反编译字节码来确认这一点。

    那么这与类型推断有什么关系呢?好吧,当您调用 foo()(1) 时,实际上是在调用 foo().invoke(1) 时带有一点语法糖,这使得更容易理解推理失败的原因。点运算符的右侧不能用于推断左侧的类型,因为必须首先评估左侧。所以foo 的类型必须明确声明为foo&lt;Int&gt;

    【讨论】:

      【解决方案3】:

      只是玩了一下,分享了一些想法,基本上回答了最后一个问题“我如何编写函数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
      

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

      否则,我不知道为什么会推断出它,为什么不推断出来。

      【讨论】:

        猜你喜欢
        • 2020-01-24
        • 2012-11-21
        • 1970-01-01
        • 2022-01-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多