在Scala中,何时使用案例类(或案例对象)与扩展枚举有什么最佳实践指南吗?

它们似乎提供了一些相同的好处。


当前回答

对于那些仍在寻找盖茨的答案的人来说: 你可以在声明case对象后引用它来实例化它:

trait Enum[A] {
  trait Value { self: A =>
    _values :+= this
  }
  private var _values = List.empty[A]
  def values = _values
}

sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
  case object EUR extends Currency; 
  EUR //THIS IS ONLY CHANGE
  case object GBP extends Currency; GBP //Inline looks better
}

其他回答

我见过让case类模拟枚举的各种版本。以下是我的看法:

trait CaseEnumValue {
    def name:String
}

trait CaseEnum {
    type V <: CaseEnumValue
    def values:List[V]
    def unapply(name:String):Option[String] = {
        if (values.exists(_.name == name)) Some(name) else None
    }
    def unapply(value:V):String = {
        return value.name
    }
    def apply(name:String):Option[V] = {
        values.find(_.name == name)
    }
}

它允许你构造如下所示的case类:

abstract class Currency(override name:String) extends CaseEnumValue {
}

object Currency extends CaseEnum {
    type V = Site
    case object EUR extends Currency("EUR")
    case object GBP extends Currency("GBP")
    var values = List(EUR, GBP)
}

也许有人可以想出一个更好的技巧,而不是像我这样简单地向列表中添加一个each case类。这是我当时所能想到的。

如果你想要维护与其他JVM语言(如Java)的互操作性,那么最好的选择是编写Java枚举。这些功能在Scala和Java代码中都可以透明地工作,这在Scala中是做不到的。枚举或case对象。如果可以避免的话,让我们不要为GitHub上的每个新爱好项目都创建一个新的枚举库!

我在这里有一个简单的库,允许你使用密封的trait /类作为枚举值,而不必维护自己的值列表。它依赖于一个简单的宏,不依赖于有bug的knownDirectSubclasses。

https://github.com/lloydmeta/enumeratum

更新: 一个新的基于宏的解决方案已经创建,它远远优于我下面概述的解决方案。我强烈推荐使用这种新的基于宏的解决方案。Dotty的计划似乎将使这种枚举解决方案成为语言的一部分。Whoohoo !

简介: 尝试在Scala项目中重现Java Enum有三种基本模式。三种模式中的两种;直接使用Java Enum和scala。枚举,不能启用Scala的穷举模式匹配。第三个;“密封特质+格对象”,是否…但是有JVM类/对象初始化的复杂性,导致不一致的序号索引生成。

I have created a solution with two classes; Enumeration and EnumerationDecorated, located in this Gist. I didn't post the code into this thread as the file for Enumeration was quite large (+400 lines - contains lots of comments explaining implementation context). Details: The question you're asking is pretty general; "...when to use caseclassesobjects vs extending [scala.]Enumeration". And it turns out there are MANY possible answers, each answer depending on the subtleties of the specific project requirements you have. The answer can be reduced down to three basic patterns.

首先,让我们确保使用的是与枚举相同的基本概念。让我们主要根据Java 5(1.5)提供的Enum定义一个枚举:

It contains a naturally ordered closed set of named members There is a fixed number of members Members are naturally ordered and explicitly indexed As opposed to being sorted based on some inate member queriable criteria Each member has a unique name within the total set of all members All members can easily be iterated through based on their indexes A member can be retrieved with its (case sensitive) name It would be quite nice if a member could also be retrieved with its case insensitive name A member can be retrieved with its index Members may easily, transparently and efficiently use serialization Members may be easily extended to hold additional associated singleton-ness data Thinking beyond Java's Enum, it would be nice to be able to explicitly leverage Scala's pattern matching exhaustiveness checking for an enumeration

接下来,让我们来看看三种最常见的解决方案模式: A)实际上直接使用Java Enum模式(在Scala/Java混合项目中):

public enum ChessPiece {
    KING('K', 0)
  , QUEEN('Q', 9)
  , BISHOP('B', 3)
  , KNIGHT('N', 3)
  , ROOK('R', 5)
  , PAWN('P', 1)
  ;

  private char character;
  private int pointValue;

  private ChessPiece(char character, int pointValue) {
    this.character = character; 
    this.pointValue = pointValue;   
  }

  public int getCharacter() {
    return character;
  }

  public int getPointValue() {
    return pointValue;
  }
}

枚举定义中的以下项不可用:

如果一个成员也可以用它不区分大小写的名字来检索,那就太好了 7 -超越Java的Enum,如果能够显式地利用Scala的模式匹配耗尽性检查枚举就好了

对于我目前的项目,我没有在Scala/Java混合项目路径上冒险的好处。即使我可以选择做一个混合项目,如果/当我添加/删除枚举成员或编写一些新代码来处理现有的枚举成员时,第7项对于允许我捕获编译时问题也是至关重要的。 B)使用“密封特征+格对象”模式:

sealed trait ChessPiece {def character: Char; def pointValue: Int}
object ChessPiece {
  case object KING extends ChessPiece {val character = 'K'; val pointValue = 0}
  case object QUEEN extends ChessPiece {val character = 'Q'; val pointValue = 9}
  case object BISHOP extends ChessPiece {val character = 'B'; val pointValue = 3}
  case object KNIGHT extends ChessPiece {val character = 'N'; val pointValue = 3}
  case object ROOK extends ChessPiece {val character = 'R'; val pointValue = 5}
  case object PAWN extends ChessPiece {val character = 'P'; val pointValue = 1}
}

枚举定义中的以下项不可用:

1.2 -成员自然有序并显式建立索引 2 -所有成员都可以根据它们的索引进行迭代 成员可以通过其名称(区分大小写)进行检索 如果一个成员也可以用它不区分大小写的名字来检索,那就太好了 成员可以用它的索引来检索

它确实符合枚举定义第5和6项,这是有争议的。对于5人来说,说它是有效的有点牵强。对于6来说,扩展以保存额外的关联单例数据并不容易。 C)使用scala。枚举模式(受StackOverflow的启发):

object ChessPiece extends Enumeration {
  val KING = ChessPieceVal('K', 0)
  val QUEEN = ChessPieceVal('Q', 9)
  val BISHOP = ChessPieceVal('B', 3)
  val KNIGHT = ChessPieceVal('N', 3)
  val ROOK = ChessPieceVal('R', 5)
  val PAWN = ChessPieceVal('P', 1)
  protected case class ChessPieceVal(character: Char, pointValue: Int) extends super.Val()
  implicit def convert(value: Value) = value.asInstanceOf[ChessPieceVal]
}

枚举定义中的以下项不可用(恰好与直接使用Java Enum的列表相同):

如果一个成员也可以用它不区分大小写的名字来检索,那就太好了 7 -超越Java的Enum,如果能够显式地利用Scala的模式匹配耗尽性检查枚举就好了

同样,对于我当前的项目,第7项对于允许我在添加/删除枚举成员或编写一些新代码来处理现有枚举成员时捕获编译时问题至关重要。


因此,给定上面的枚举定义,以上三个解决方案都不能工作,因为它们不能提供上面枚举定义中概述的所有内容:

Java Enum直接在混合Scala/Java项目 “密封trait + case对象” scala。枚举

Each of these solutions can be eventually reworked/expanded/refactored to attempt to cover some of each one's missing requirements. However, neither the Java Enum nor the scala.Enumeration solutions can be sufficiently expanded to provide item 7. And for my own projects, this is one of the more compelling values of using a closed type within Scala. I strongly prefer compile time warnings/errors to indicate I have a gap/issue in my code as opposed to having to glean it out of a production runtime exception/failure.


在这方面,我开始使用case对象路径,看看是否可以生成一个涵盖上述所有枚举定义的解决方案。第一个挑战是解决JVM类/对象初始化的核心问题(在这篇StackOverflow文章中有详细介绍)。我终于找到了解决办法。

我的解决方案有两个特点;Enumeration和enumerationented,并且由于Enumeration trait超过+400行长(许多注释解释上下文),我放弃将其粘贴到这个线程(这将使它延伸到页面相当大)。详情请直接跳到主旨。

下面是使用与上面相同的数据思想(此处提供完整的注释版本)并在enumerationdecoration中实现的解决方案。

import scala.reflect.runtime.universe.{TypeTag,typeTag}
import org.public_domain.scala.utils.EnumerationDecorated

object ChessPiecesEnhancedDecorated extends EnumerationDecorated {
  case object KING extends Member
  case object QUEEN extends Member
  case object BISHOP extends Member
  case object KNIGHT extends Member
  case object ROOK extends Member
  case object PAWN extends Member

  val decorationOrderedSet: List[Decoration] =
    List(
        Decoration(KING,   'K', 0)
      , Decoration(QUEEN,  'Q', 9)
      , Decoration(BISHOP, 'B', 3)
      , Decoration(KNIGHT, 'N', 3)
      , Decoration(ROOK,   'R', 5)
      , Decoration(PAWN,   'P', 1)
    )

  final case class Decoration private[ChessPiecesEnhancedDecorated] (member: Member, char: Char, pointValue: Int) extends DecorationBase {
    val description: String = member.name.toLowerCase.capitalize
  }
  override def typeTagMember: TypeTag[_] = typeTag[Member]
  sealed trait Member extends MemberDecorated
}

这是我创建的一对新枚举特征(位于Gist中)的示例使用,用于实现枚举定义中所期望和概述的所有功能。

所表达的一个关注点是枚举成员名必须重复(上面示例中的decorationOrderedSet)。虽然我确实把它减少到一次重复,但由于两个问题,我不知道如何让它更少:

这个特定对象/case对象模型的JVM对象/类初始化是未定义的(参见这个Stackoverflow线程) 方法getClass返回的内容。getDeclaredClasses有一个未定义的顺序(它不太可能与源代码中的case对象声明的顺序相同)

考虑到这两个问题,我不得不放弃尝试生成隐含的排序,而必须显式地要求客户端用某种有序集概念定义和声明它。由于Scala集合没有插入有序集实现,所以我能做的最好的事情就是使用List,然后运行时检查它是否真的是一个集合。这不是我想要的实现方式。

And given the design required this second list/set ordering val, given the ChessPiecesEnhancedDecorated example above, it was possible to add case object PAWN2 extends Member and then forget to add Decoration(PAWN2,'P2', 2) to decorationOrderedSet. So, there is a runtime check to verify that the list is not only a set, but contains ALL of the case objects which extend the sealed trait Member. That was a special form of reflection/macro hell to work through. Please leave comments and/or feedback on the Gist.

2017年3月更新:正如Anthony Accioly所评论的,scala。枚举/枚举PR已关闭。

Dotty (Scala的下一代编译器)将占据主导地位,尽管Dotty发行于1970年,Martin Odersky的PR发行于1958年。


注意:现在(2016年8月,6年多之后)有一个移除scala的提案。编号:PR 5352

scala抨击。枚举,添加@enum注释 的语法

@enum
 class Toggle {
  ON
  OFF
 }

是一个可能的实现示例,目的是也支持符合某些限制(没有嵌套,递归或变化的构造函数参数)的adt,例如:

@enum
sealed trait Toggle
case object ON  extends Toggle
case object OFF extends Toggle

Deprecates the unmitigated disaster that is scala.Enumeration. Advantages of @enum over scala.Enumeration: Actually works Java interop No erasure issues No confusing mini-DSL to learn when defining enumerations Disadvantages: None. This addresses the issue of not being able to have one codebase that supports Scala-JVM, Scala.js and Scala-Native (Java source code not supported on Scala.js/Scala-Native, Scala source code not able to define enums that are accepted by existing APIs on Scala-JVM).