在Scala最佳实践中使用枚举

esbemjvw  于 2022-11-09  发布在  Scala
关注(0)|答案(2)|浏览(153)

我一直在使用密封的特征和Case对象在Scala中定义枚举类型,最近我遇到了另一种在Scala中扩展枚举类的方法,如下所示:

object CertificateStatusEnum extends Enumeration {
  val Accepted, SignatureError, CertificateExpired, CertificateRevoked, NoCertificateAvailable, CertChainError, ContractCancelled = Value
}

反对做这样的事情:

sealed trait CertificateStatus
object CertificateStatus extends {
  case object Accepted               extends CertificateStatus
  case object SignatureError         extends CertificateStatus
  case object CertificateExpired     extends CertificateStatus
  case object CertificateRevoked     extends CertificateStatus
  case object NoCertificateAvailable extends CertificateStatus
  case object CertChainError         extends CertificateStatus
  case object ContractCancelled      extends CertificateStatus
}

什么被认为是好的方法?

yduiuuwa

yduiuuwa1#

它们都是出于简单的目的来完成工作,但就最佳实践而言,sealed traits+case objects的使用更为灵活。
背后的故事是,因为Scala附带了Java拥有的一切,所以Java有枚举,Scala出于互操作性的原因不得不把它们放在那里。但Scala不需要它们,因为它支持ADT(代数数据类型),因此可以像您刚才看到的那样以函数方式生成枚举。
对于普通的Enumeration类,您将遇到某些限制:

  • 编译器无法彻底检测模式匹配
  • 除了String名称和Int id之外,扩展元素以容纳更多数据实际上更难,因为Value是最终的。
  • 在运行时,由于类型擦除,所有枚举都具有相同的类型,因此限制了类型级别编程-例如,您不能拥有重载方法。
  • 当您执行object CertificateStatusEnum extends Enumeration时,您的枚举将不会被定义为CertificateStatusEnum类型,而是定义为CertificateStatusEnum.Value-因此您必须使用一些类型别名来修复它。这样做的问题是您的同伴的类型仍然是CertificateStatusEnum.Value.type,因此您最终将使用多个别名来解决这个问题,并且会有一个相当令人困惑的枚举。

另一方面,代数数据类型是一种类型安全的替代方法,您可以指定每个元素的形状并对枚举进行编码,您只需要使用sealed traits(或abstract classes)和case objects精确表示的sum类型即可。
这些解决了Enumeration类的限制,但您还会遇到其他一些(次要的)缺陷,尽管这些不是很大的限制:

  • Case对象没有默认顺序--因此,如果您需要一个默认顺序,您必须将id作为属性添加到sealed trait中,并提供排序方法。
  • 一个有些问题的问题是,即使case objects是可序列化的,如果您需要反序列化您的枚举,也没有简单的方法来从其枚举名反序列化Case对象。您很可能需要编写一个定制的反序列化程序。
  • 默认情况下,您不能像使用Enumeration那样遍历它们。但这并不是一个非常常见的用例。然而,它可以很容易地实现,例如:
object CertificateStatus extends {
    val values: Seq[CertificateStatus] = Seq(
      Accepted,
      SignatureError,
      CertificateExpired,
      CertificateRevoked,
      NoCertificateAvailable,
      CertChainError,
      ContractCancelled
    )
    // rest of the code
  }

在实践中,您可以使用Enumeration做任何事情,也可以使用sealed trait+case objects做任何事情。因此,前者脱离了人们的偏好,转而支持后者。这种比较只与Scala 2有关。
在Scala3中,它们将ADT及其泛化版本(GADT)与枚举统一在一个新的强大语法下,有效地为您提供了所需的一切。所以你有充分的理由使用它们。正如盖尔提到的那样,他们成为了一流的实体。

lf5gs5x2

lf5gs5x22#

这取决于您希望从枚举中获得什么。
在第一种情况下,您隐式拥有对项目的订单(通过id属性访问)。重新排序是有后果的。
我更喜欢‘Case Object’,在某些情况下,枚举项可以在构造函数中有额外的信息(例如,带RGB的颜色,而不仅仅是名称)。
另外,我建议使用https://index.scala-lang.org/mrvisser/sealerate或类似的库。这允许迭代所有元素。

相关问题