【问题标题】:Kotlin and discriminated unions (sum types)Kotlin 和有区别的联合(求和类型)
【发布时间】:2015-04-26 01:03:23
【问题描述】:

Kotlin 是否有类似区分联合(求和类型)的东西?这个(F#)的惯用 Kotlin 翻译是什么:

type OrderMessage =
    | New of Id: int * Quantity: int
    | Cancel of Id: int

let handleMessage msg = 
    match msg with
        | New(id, qty) -> handleNew id qty
        | Cancel(id) -> handleCxl id

【问题讨论】:

    标签: kotlin algebraic-data-types discriminated-union


    【解决方案1】:

    一个人会做这样的事情:

    sealed class Either<out A, out B>
    class L<A>(val value: A) : Either<A, Nothing>()
    class R<B>(val value: B) : Either<Nothing, B>()
    
    fun main() {
        val x = if (condition()) {
            L(0)
        } else {
            R("")
        }
        use(x)
    }
    
    fun use(x: Either<Int, String>) = when (x) {
        is L -> println("It's a number: ${x.value}")
        is R -> println("It's a string: ${x.value}")
    }
    

    【讨论】:

      【解决方案2】:

      Kotlin 中的密封类被设计为能够表示求和类型,就像 Scala 中的密封特性一样。

      例子:

      sealed class OrderStatus {
          object Approved: OrderStatus()
          class Rejected(val reason: String): OrderStatus()
      }
      

      当您在匹配的 when 表达式中使用密封类时,使用密封类的主要好处就会发挥作用。

      如果可以验证语句涵盖所有情况,则无需在语句中添加 else 子句。

      private fun getOrderNotification(orderStatus:OrderStatus): String{
          return when(orderStatus) {
              is OrderStatus.Approved -> "The order has been approved"
              is OrderStatus.Rejected -> "The order has been rejected. Reason:" + orderStatus.reason
         }
      }
      

      有几点需要牢记:

      • 在 Kotlin 中执行 smartcast 时,这意味着在此示例中无需执行从 OrderStatus 到 OrderStatus.Rejected 的转换即可访问原因属性。

      • 如果我们没有定义如何处理被拒绝的情况,编译将失败,并且在 IDE 中会出现如下警告:

      'when' 表达式必须是详尽的,添加必要的 'is Rejected' 分支或 'else' 分支。

      • 当它可以用作表达式或语句时。如果用作表达式,则满足分支的值成为通用表达式的值。如果用作语句,则忽略各个分支的值。这意味着缺少分支的情况下的编译错误只有在用作表达式时才会发生,使用结果。

      这是我博客的链接(西班牙语),我在其中有一篇关于 ADT 的更完整的文章以及 kotlin 示例:http://xurxodev.com/tipos-de-datos-algebraicos/

      【讨论】:

        【解决方案3】:

        Kotlin's sealed class 解决该问题的方法与Scala sealed class and sealed trait 极为相似。

        示例(取自链接的 Kotlin 文章):

        sealed class Expr {
            class Const(val number: Double) : Expr()
            class Sum(val e1: Expr, val e2: Expr) : Expr()
            object NotANumber : Expr()
        }
        

        【讨论】:

          【解决方案4】:

          在 OO 语言(例如 Kotlin 或 Scala)中实现这种抽象的常用方法是通过继承:

          open class OrderMessage private () { // private constructor to prevent creating more subclasses outside
              class New(val id: Int, val quantity: Int) : OrderMessage()
              class Cancel(val id: Int) : OrderMessage()
          }
          

          如果您愿意,可以将公共部分推送到超类:

          open class OrderMessage private (val id: Int) { // private constructor to prevent creating more subclasses outside
              class New(id: Int, val quantity: Int) : OrderMessage(id)
              class Cancel(id: Int) : OrderMessage(id)
          }
          

          类型检查器不知道这样的层次结构是封闭的,所以当你对它进行类似大小写的匹配(when-expression)时,它会抱怨它并不详尽,但这会被修复很快。

          更新:虽然 Kotlin 不支持 模式匹配,但您可以使用 when-表达式作为智能转换来获得几乎相同的行为:

          when (message) {
            is New -> println("new $id: $quantity")
            is Cancel -> println("cancel $id")
          }
          

          查看更多关于智能投射的信息here

          【讨论】:

          • 您好,感谢您的回复!在 Scala 中,我会使用 sealed Trait OrderMessagecase class New(..) extends OrderMessage 等。然后我可以对订单消息类型进行模式匹配,并以相同类型访问它们的字段(就像上面的 F# 示例一样)。我们有没有可能很快在 Kotlin 中使用 when 做到这一点? :)
          • @enhmark 你可以这样做:kotlin-demo.jetbrains.com/…。查看更多关于智能演员的信息:kotlinlang.org/docs/reference/typecasts.html#smart-casts
          • @AndreyBreslav “会抱怨详尽无遗”。你忘了在那儿加上“不”吗?否则我不明白你回答的那部分。
          • @HRJ 是的,我做到了
          • Kotlin 现在有Sealed Classes,它允许您控制可能的层次结构并让编译器检查您是否用尽了when 语句/表达式中的所有选项。这解决了 Andrey 和 @HRJ 提到的问题。
          猜你喜欢
          • 2021-02-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-07-15
          • 1970-01-01
          • 2021-12-04
          • 2012-10-14
          相关资源
          最近更新 更多