应用类型投影时,类型参数如何变成 Nothing、Any?或类型界限等?

发布于 2025-01-18 01:19:03 字数 3022 浏览 0 评论 0原文

我在 Kotlin 中发现了一些对我来说一直很直观的东西,但我最近意识到我无法解释其中的底层细节。

考虑一下以下代码中 x.returnT 的返回类型以及 consumeTaMoreComplicatedCase 的参数类型:

val x: Foo<*> = TODO()

class Foo<T> {
    fun returnT(): T = TODO()
    fun consumeT(t: T) {}
    fun aMoreComplicatedCase(func: (T) -> T) {}
}

IntelliJ 告诉我x.returnT 返回 Any?consumeT 接受 NothingaMoreComplicatedCase 接受(有吗?)->什么都没有。

输入图片此处描述

使用规范,我们知道* 大致意味着同时 out Any?in Nothing,并应用 PECS (或者这应该在 Kotlin 中称为 POCI,因为生产者 out 消费者 in< /代码>),我们知道xAny? 的生产者,但什么也没有的消费者。

更直观的解释是,* 的意思是“我们对 Foo<> 中的内容一无所知”,并且因此,当然 returnT 可以返回任何内容,甚至可以为 null 的内容。同样,当然 consumeT 不能接受任何东西 - 它需要 T,具体是什么我们不知道。

类似的参数可以应用于 out SomeTypein SomeType 的情况。

我陷入困境的地方我

已经用Java编程很长时间了,我根本没想到会出现这样的结果。我期望类似于 IntelliJ 对类似 Java 代码所说的内容:

public class Main {
    public static void main(String[] args) {
        Foo<?> foo = new Foo<>();
    }
}
class Foo<T> {
    public T returnT() { return null; }
    public void consumeT(T t) { }
    public void aMoreComplicatedCase(Function<T, T> func) { }
}

IntelliJ 说 x.returnT 返回 ? 的捕获,consumeT 需要? 的捕获,以及 aMoreComplicatedCase 接受 Function:

在此处输入图像描述

我查看了 Kotlin 规范,发现 Kotlin 也有 捕获类型,它们是具有一定边界的新类型变量。 那么 NothingAny? 从何而来?或者在使用 in SomeType 作为投影的情况下, consumeT 将采用 SomeType 代替。 为什么它是 SomeType,而不是以 SomeType 作为其子类型的捕获类型? 毕竟,规范在类型捕获部分中说:

  • 对于 Ai 中的逆变类型参数 Ai,如果 Fi 是协变类型参数,则 Ki 是格式错误的类型。否则,Ki :>艾。
  • 对于双变量类型参数 kotlin.Nothing <: Ki <: kotlin.Any?

我猜测之后会发生其他事情类型捕获?这个过程叫什么?或者这里是否有一个完全不同的过程,不是类型捕获?

(我猜这也有可能只是“当类型是捕获类型时,IntelliJ/kotlinc 使用什么字符串表示形式进行显示”的问题,与类型系统本身无关......)

I found something in Kotlin that had always been intuitive to me, but I recently realised I can't explain the low level details of this.

Consider what the return type of x.returnT, and the parameter types of consumeT and aMoreComplicatedCase would be in the below code:

val x: Foo<*> = TODO()

class Foo<T> {
    fun returnT(): T = TODO()
    fun consumeT(t: T) {}
    fun aMoreComplicatedCase(func: (T) -> T) {}
}

IntelliJ tells me that x.returnT returns Any?, consumeT takes Nothing, and aMoreComplicatedCase takes (Any?) -> Nothing.

enter image description here

Using the spec, we know that * roughly means out Any? and in Nothing at the same time, and applying PECS (Or maybe this should be called POCI in Kotlin since producer out consumer in), we know x is a producer of Any? and a consumer of nothing.

An even more intuitive explanation is that * means "we know nothing about what goes in the <> of Foo at all", and because of that, of course returnT can return anything, even nullable things. Similarly, of course consumeT can't take anything - it takes T, the exact thing we don't know.

Similar arguments can be applied to cases where it is out SomeType or in SomeType.

Where I am stuck

Having been programming in Java for a long time, I did not expect this result at all. I expected something similar to what IntelliJ would say for the similar Java code:

public class Main {
    public static void main(String[] args) {
        Foo<?> foo = new Foo<>();
    }
}
class Foo<T> {
    public T returnT() { return null; }
    public void consumeT(T t) { }
    public void aMoreComplicatedCase(Function<T, T> func) { }
}

IntelliJ says that x.returnT returns a capture of ?, consumeT takes a capture of ?, and aMoreComplicatedCase takes Function<capture of ?, capture of ?>:

enter image description here

I looked around the Kotlin spec and found that Kotlin also have captured types, which are fresh type variables with certain bounds. So where does Nothing and Any? come from? Or in the case of using in SomeType as the projection, consumeT will take a SomeType instead. Why is it SomeType, but not a capture type with SomeType as its subtype? After all, the spec says in the type capturing section:

  • For a contravariant type argument Ai in Ai, if Fi is a covariant type parameter, Ki is an ill-formed type. Otherwise, Ki :> Ai.
  • For a bivariant type argument , kotlin.Nothing <: Ki <: kotlin.Any?

I'm guessing something else happens after type capturing? What is that process called? Or is there a totally different process that is not type capturing going on here?

(I guess there is also the possibility that this is just a matter of "what string representation IntelliJ/kotlinc uses for display, when the type is a captured type" and has nothing to do with the type system itself...)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文