Kotlin和区分联合类型(总和类型)

68

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
4个回答

65

1
这是Kotlin编码代数数据类型的方式,但显然有一个普遍误解,即代数数据类型或和类型会成为联合类型。代数数据类型定义了新的类型名称,但不会将现有的类型约束组合成新的类型。作为一种结构类型操作,联合类型比纯粹的名义类型的代数数据类型更加灵活。 我在Kotlin方面还是个初学者,但据我理解,这个密封类不允许你表达像Int | Double这样的内容。 - ChrisoLosoph
1
据我所知,目前在Kotlin中没有办法表示Int | Double。话虽如此,自从2018年以来,我就没有再接触过这门语言了,它可能已经有所发展。如果你有相关的知识或者在任何时候获得了相关信息,我很感兴趣了解更多。@ChrisoLosoph :-) - Adeynack

39

在面向对象语言(例如 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)
}

类型检查器并不知道这样的层次结构是封闭的,所以当您对其进行类似case的匹配(when-expression)时,它会抱怨它不够全面,但这很快就会得到解决。

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

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

了解更多关于智能类型转换的内容,请点击这里


1
嗨,谢谢您的回复!在Scala中,我会使用sealed Trait OrderMessagecase class New(..) extends OrderMessage等。然后,我就可以在订单消息类型上进行模式匹配,并以同一类型访问它们的字段(就像上面的F#示例一样)。有没有可能我们很快就能用Kotlin中的when做到这一点呢? :) - ehnmark
@enhmark 你可以这样做:http://kotlin-demo.jetbrains.com/?publicLink=104074971561017308771-1714234109。了解更多关于智能转换的信息,请参阅:http://kotlinlang.org/docs/reference/typecasts.html#smart-casts。 - Andrey Breslav
@AndreyBreslav会抱怨穷尽性。你是不是忘了加一个“不”呢?否则我就不理解你回答的那部分了。 - HRJ
@HRJ 有点儿,是的,我做了。 - Andrey Breslav
34
Kotlin现在引入了“密封类”(Sealed Classes),可以帮助你控制可能的继承体系,并让编译器检查在 when 语句/表达式中是否已覆盖所有选项。这解决了Andrey和@HRJ提到的问题。 - Jayson Minard

11
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中进行智能类型转换时,这意味着在此示例中,不需要将OrderStatus转换为OrderStatus.Rejected即可访问reason属性。

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

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

  • when可以用作表达式或语句。如果用作表达式,则满足条件的分支的值成为通用表达式的值。如果用作语句,则忽略各个分支的值。这意味着,在当作表达式使用并使用结果时,如果缺少分支,则会出现编译错误。

这是我的博客链接(西班牙语),其中有更详细的关于ADT的Kotlin示例文章:http://xurxodev.com/tipos-de-datos-algebraicos/


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}")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接