【问题标题】:Why does Generic Algebraic Data Types require `T` on member types?为什么通用代数数据类型在成员类型上需要“T”?
【发布时间】:2016-06-25 14:13:20
【问题描述】:

我想定义一个通用代数数据类型以用于我的parse 函数,如下所示:

sealed class Result<T> {
    class Success(val value: T, val pos: Int) : Result<T>()
    class Failure(val message: String, val pos: Int) : Result<T>()
}

fun <T> parse(t: Parser<T>, input: String, initialPos: Int = 0, collectErrors: Boolean = true) : Result<T> {

但是这是不允许的,因为 T 是一个未定义的引用。

如果我将T 添加到所有成员类型,它会起作用:

sealed class Result<T> {
    class Success<T>(val value: T, val pos: Int) : Result<T>()
    class Failure<T>(val message: String, val pos: Int) : Result<T>()
}

对我来说,这有点令人困惑,这让我相信我在这里遗漏了一些东西。为什么在第一种情况下定义成员类型时看不到T

此外,在创建Success 的实例时,我希望语法为:

Result<T>.Success<T>(tv.someValue, pos)

但这行不通,而是我这样做:

Result.Success<T>(tv.someValue, pos)

这对我来说是更可取的语法,但我很难理解为什么我应该在此处的 Result 中省略 T

【问题讨论】:

    标签: generics kotlin


    【解决方案1】:

    Result 是一个泛型类,有一个名为 T 的泛型参数。不过,类名是 Result,而不是 Result&lt;T&gt;

    Success 也是一个泛型类。因此,由于它是通用的,您需要将其定义为Success&lt;T&gt;。如果你不这样做,那么它就不再是通用的了。请注意,即使它是泛型的 Result 的子类,它也可能是非泛型类型。例如:

    class Success(val value: String, val pos: Int) : Result<String>()
    

    还要注意,虽然 Result 和 Failure 是泛型的,但它们不会将它们的泛型类型用于任何事情。所以你实际上可以将你的类定义为

    sealed class Result {
        class Success<T>(val value: T, val pos: Int) : Result()
        class Failure(val message: String, val pos: Int) : Result()
    }
    

    现在,为什么需要使用Result.Success&lt;T&gt;(tv.someValue, pos) 而不是Result&lt;T&gt;.Success&lt;T&gt;(tv.someValue, pos)

    因为类的名称是Result.Success。参数类型不是类名的一部分。大多数时候,根本不需要指定它,因为它会被推断出来:

    val r = Result.Success("foo", 1)
    

    创建Success&lt;String&gt; 的实例。如果您想创建 Success&lt;CharSequence&gt;,则必须明确指定泛型类型:

    val r = Result.Success<CharSequence>("foo", 1)
    

    val r: Result.Success<CharSequence> = Result.Success("foo", 1)
    

    【讨论】:

    • 谢谢。我意识到Result 严格来说不需要是泛型的,但我喜欢它,因为parse 产生一个Result,其中Success&lt;T&gt; 泛型类型与Parser&lt;T&gt; 相同。如果我不在结果中包含 T,我(可能是错误地)相信 API 的类型安全性会降低。
    • 如果我想要像fun foo(): Result&lt;SomeType&gt; 这样的方法签名怎么办。这样返回的值要么是 Success 要么是 Failure?
    【解决方案2】:

    规则与 Java 相同。它基本上归结为SuccessFailureResult 的静态嵌套类。 Kotlin 中没有“成员类型”,静态嵌套类是可以访问外部类范围的常规类。而如果一个类扩展了一个泛型超类,它总是需要绑定泛型类型参数。

    相比之下,非静态嵌套类(由inner 关键字表示)总是带有外部类的泛型类型参数。这样您就可以构建以下类型层次结构:

    open class Foo<T> {
        inner class Bar : Foo<T>()
    }
    

    要实例化Bar,您需要有一个Foo 的实例:

    val b = Foo<String>().Bar()
    

    【讨论】:

    • 谢谢。我仍然有些困惑,为什么 T 对静态内部类不可见,因为我认为 T 将成为范围的一部分。
    • 我想这就是它在 Java 中的工作方式,在 Kotlin 中也是如此。您来自一种工作方式不同的语言吗?
    • @FuleSnabel 静态嵌套类(没有静态内部类之类的东西)与顶级类没有什么不同。唯一的区别是它们有一个更奇怪的名称(Result.Success 而不是 Success),并且它们可以访问其封闭类的私有成员(反之亦然)。除此之外,它们就像顶级课程。内部类是不同的,因为它们隐式引用了它们的所有者 instance
    • 我正在与我在 OCaml 的经验进行比较,比如我会声明 ADT 的语言,有点像这样:type Result&lt;'T&gt; = Success of 'T*int | Failure of String*int
    • Kotlin 可以 使语法成为可能,但我猜设计师认为它没有提供太多价值。您需要记住,sealed 类只是常规的抽象类,其中所有继承者都是已知的,仅此而已。
    【解决方案3】:

    如果你像这样使用输出方差,它应该可以工作,

    sealed class Result<out T> {
      data class Success<out T>(val value: T, val pos: Int) : Result<T>()
      data class Failure(val message: String, val pos: Int) : Result<Nothing>()
    }
    

    【讨论】:

      【解决方案4】:

      在您的示例中,有三个不同的通用参数,而不是一个。 IE。您的代码相当于:

      sealed class Result<I> {
          class Success<A>(val value: A, val pos: Int) : Result<A>()
          class Failure<B>(val message: String, val pos: Int) : Result<B>()
      }
      

      但是其他答案如何提及,您不使用IB参数,因此最好省略它们。

      【讨论】:

      • 属性value 的类型为T。您的代码无法编译。
      猜你喜欢
      • 2015-10-23
      • 1970-01-01
      • 2018-05-09
      • 2017-06-05
      • 2017-10-23
      • 2018-11-13
      • 2018-12-08
      • 1970-01-01
      • 2022-09-28
      相关资源
      最近更新 更多