【问题标题】:Kotlin - Generic Type Parameters Not Being RespectedKotlin - 不尊重泛型类型参数
【发布时间】:2019-08-01 23:27:02
【问题描述】:

考虑以下示例:

import kotlin.reflect.KProperty1

infix fun <T, R> KProperty1<T, R>.test(value: R) = Unit

data class Foo(val bar: Int)

fun main() {
    Foo::bar test "Hello"
}

鉴于test 需要R 类型的value,为什么在这种情况下,属性类型为Int,它是否允许我传递String

【问题讨论】:

  • 最有可能推断为Any。至少可以编译:Foo::bar.test&lt;Foo, Any&gt;("Hello"),它不会使用StringInt 而不是Any 进行编译。
  • 您的问题似乎与KProperty1 上的out R-定义有关...如果不存在,则在传递String 时会出现预期的编译时错误。 ..
  • 我认为stackoverflow.com/a/52720842/4265739 的第一句话回答了你的问题
  • 作为一种解决方法(但可能有更好的方法来支持你想要的),你可以将 kproperty 传递给生产者函数,例如:inline infix fun &lt;T, U, R : U&gt; KProperty1&lt;T, U&gt;.test(block : (KProperty1&lt;T, U&gt;) -&gt; R) = block(this),用法如下:@ 987654337@。这样,如果您返回与 R : U 不同的任何内容,编译器会警告您...类似的事情也可以通过使用您自己的持有请求类型的中间类型来完成...(例如 Foo::bar.toYourIntermediate() test 3

标签: generics kotlin type-inference typechecking type-parameter


【解决方案1】:

首先看一下接口KProperty1的声明,即:

interface KProperty1<T, out R> : KProperty<R>, (T) -> R

这里的重要部分是out-projected 类型参数R,它定义了KProperty1 类型与用于R 的不同类型参数之间的子类型关系。

(1) 即,对于任何FooAB,使得A : BA 是@987654335 的子类型@),
KProperty1&lt;Foo, A&gt; : KProperty1&lt;Foo, B&gt;.这称为协方差,因为参数化类型之间的关联方式与其类型参数的关联方式相同。


(2) 接下来,请注意,对于任何 AB 使得 A : BA 的实例可以作为任何B 类型参数的参数。扩展函数的接收器参数在这方面与普通参数没有区别。


现在,关键部分是编译器运行的类型推断算法。类型推断的目标之一是为每个省略类型参数的泛型调用建立静态已知的类型参数。

在调用Foo::bar test "Hello" 的类型推断期间,编译器需要根据接收者Foo::bar (KProperty1&lt;Foo, Int&gt;) 和@987654347 的已知类型实际推断TR 的类型参数@参数"Hello" (String)。

这是通过解决约束系统在内部完成的。我们可以模拟这个逻辑如下:

  • 鉴于KProperty&lt;Foo, Int&gt; 被传递为KProperty&lt;T, R&gt;

    • 我们必须使用T := Foo(因为T是不变的)
    • 我们必须使用Int 或其任何超类型作为类型参数R
      • 这是因为 R 的协方差:给定 (1)(2) 组合, 为 R 选择 Int 或其某些超类型是必要的,以便能够通过 KProperty&lt;Foo, Int&gt; 预期 KProperty&lt;Foo, R&gt; 的位置
      • 这些超类型的示例是Int?NumberNumber?AnyAny?
  • 假设String 被传递为R

    • 我们必须使用String 或其某些超类型作为R
      • 这是能够传递 String 所必需的,因为 (2) 预计 R
      • 这些超类型的示例是String?CharSequenceCharSequence?AnyAny?

鉴于R 的两个约束,即它应该是Int 或其某些超类型,它应该是String 或其某些超类型,编译器会找到同时满足两者的最不常见的类型。这个类型是Any

因此,推断的类型参数是T := FooR := Any,而带有显式类型参数的调用将是:

Foo::bar.test<Foo, Any>("Hello")

在 IntelliJ IDEA 中,您可以在非中缀调用上使用操作添加显式类型参数来添加推断类型。


免责声明:编译器内部的工作方式并不完全如此,但使用这种推理方式,您可能经常得到与编译器结果一致的结果。


也相关:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-18
    • 1970-01-01
    • 2018-07-26
    相关资源
    最近更新 更多