案例对象与Scala中的枚举

有什么关于什么时候使用case类 (或case对象)和扩展Scala中的Enumeration的最佳实践指南?

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

一个很大的区别就是Enumeration支持从一些String实例化它们。 例如:

 object Currency extends Enumeration { val GBP = Value("GBP") val EUR = Value("EUR") //etc. } 

那你可以这样做:

 val ccy = Currency.withName("EUR") 

这在希望保持枚举(例如,数据库)或者从驻留在文件中的数据创build它时非常有用。 不过,我发现一般情况下,Scala中的枚举有点笨拙,并且有一种尴尬的插件的感觉,所以我现在倾向于使用case objectcase object比enum更灵活:

 sealed trait Currency { def name: String } case object EUR extends Currency { val name = "EUR" } //etc. case class UnknownCurrency(name: String) extends Currency 

所以现在我有…的优势

 trade.ccy match { case EUR => case UnknownCurrency(code) => } 

由于@ chaotic3quilibrium指出(有一些更正,以减轻阅读):

关于“UnknownCurrency(code)”模式,除了“打破” Currencytypes的闭集性质外,还有其他一些处理找不到货币代码string的方法。 CurrencytypesUnknownCurrency现在可以潜入API的其他部分。

build议在Enumeration之外推送这个案例,并让客户端处理一个Option[Currency]types,这个types将清楚地表明存在一个匹配的问题,并鼓励API的用户自行排除。

为了跟上这里的其他答案,在Enumeration s上的case object的主要缺点是:

  1. 无法遍历“枚举”的所有实例 。 这当然是这样,但我发现在实践中这是非常罕见的,这是必需的。

  2. 不能从持久值轻松实例化 。 这也是事实,但除了大量枚举(例如所有货币)的情况外,这并不会造成巨大的开销。

Case对象已经为它们的toString方法返回它们的名字,因此不需要单独传递它们。 这里是一个类似于jho的版本(为简洁起见省略了简便的方法):

 trait Enum[A] { trait Value { self: A => } val values: List[A] } sealed trait Currency extends Currency.Value object Currency extends Enum[Currency] { case object EUR extends Currency case object GBP extends Currency val values = List(EUR, GBP) } 

对象是懒惰的; 通过使用vals,我们可以删除列表,但必须重复名称:

 trait Enum[A <: {def name: String}] { trait Value { self: A => _values :+= this } private var _values = List.empty[A] def values = _values } sealed abstract class Currency(val name: String) extends Currency.Value object Currency extends Enum[Currency] { val EUR = new Currency("EUR") {} val GBP = new Currency("GBP") {} } 

如果您不介意一些作弊行为,则可以使用reflectionAPI或Google Reflections之类的function预载您的枚举值。 非懒惰的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 case object GBP extends Currency } 

好,干净,具有案例类和Java枚举的所有优点。 就个人而言,我定义了对象之外的枚举值,以更好地匹配惯用的Scala代码:

 object Currency extends Enum[Currency] sealed trait Currency extends Currency.Value case object EUR extends Currency case object GBP extends Currency 

更新:一个新的基于macros的解决scheme已经创build,远远优于我下面概述的解决scheme。 我强烈build议使用这个新的基于macros的解决scheme 。 看来Dotty的计划将使这种风格的enum解决scheme成为语言的一部分。 Whoohoo!

概要:
试图在Scala项目中重现Java Enum有三种基本模式。 三种模式中的两种; 直接使用Java Enumscala.Enumeration ,不能使Scala的详尽模式匹配。 第三个; “密封特质+案例对象”,确实…但是有JVM类/对象初始化并发症,导致索引生成序列不一致。

我已经创build了两个类的解决scheme; 枚举和枚举装饰 ,位于这个要点 。 我没有把代码放到这个线程中,因为Enumeration的文件非常大(+400行 – 包含许多解释实现上下文的注释)。

细节:
你所问的问题相当普遍; “…何时使用case objects vs扩展[scala.]Enumeration ”。 事实certificate,有很多可能的答案,每个答案取决于你具体项目要求的细微之处。 答案可以简化为三种基本模式。

首先,让我们确定我们正在从一个枚举的基本概念开始工作。 让我们定义一个枚举,主要是从Java 5(1.5)提供的Enum方面:

  1. 它包含一个自然命令的封闭的命名成员集合
    1. 有一定数量的会员
    2. 成员自然是有序的,并明确索引
      • 而不是根据一些先前的成员可查询标准进行sorting
    3. 每个成员在全部成员中都有一个唯一的名字
  2. 所有成员都可以根据其索引轻松地迭代
  3. 一个成员可以检索其(区分大小写)名称
    1. 如果一个成员也可以用不区分大小写的名字来检索,那将是相当不错的
  4. 一个成员可以用它的索引来检索
  5. 会员可以轻松,透明,有效地使用序列化
  6. 会员可以很容易地扩展到拥有额外的相关单例数据
  7. 思考超越Java的Enum ,能够明确地利用Scala的模式匹配穷举检查枚举

接下来,我们来看看发布的三种最常见解决scheme模式的简化版本:

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; } } 

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

  1. 3.1 – 如果一个成员也可以用不区分大小写的名字来检索,那将是相当不错的
  2. 7 – 思考超越Java的枚举,能够明确利用Scala的模式匹配穷举检查枚举

对于我目前的项目,我没有在Scala / Java混合项目path上冒风险的好处。 即使我可以select做一个混合项目,项目7对于在添加/删除枚举成员时或者在编写一些新的代码来处理现有的枚举成员的时候能够捕捉编译时问题非常关键。

B)使用“ sealed trait + case objects ”模式:

 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. 1.2 – 成员自然是有序的,并且明确地被索引
  2. 2 – 所有成员都可以根据他们的索引轻松地迭代
  3. 3 – 可以检索其成员(区分大小写)的成员
  4. 3.1 – 如果一个成员也可以用不区分大小写的名字来检索,那将是相当不错的
  5. 4 – 一个成员可以检索其索引

有争议的是,它确实符合枚举定义项目5和6.对于5来说,它是高效的。 对于6来说,扩展来保存更多的相关单例数据并不容易。

C)使用scala.Enumeration模式(受此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的列表相同):

  1. 3.1 – 如果一个成员也可以用不区分大小写的名字来检索,那将是相当不错的
  2. 7 – 思考超越Java的枚举,能够明确利用Scala的模式匹配穷举检查枚举

对于我现在的项目来说,第7项对于在添加/删除枚举成员时或者在编写一些新的代码来处理现有的枚举成员时,能够捕捉编译时问题至关重要。


因此,鉴于枚举的上述定义,上述三个解决scheme都没有工作,因为它们不提供上面枚举定义中概述的所有内容:

  1. Java Enum直接在混合的Scala / Java项目中使用
  2. “密封性状+案件对象”
  3. scala.Enumeration

这些解决scheme中的每一个都可以最终被重新构build/扩展/重构,以便覆盖每个人缺less的需求。 但是,Java Enumscala.Enumeration解决scheme都不能充分扩展以提供第7项。对于我自己的项目,这是在Scala中使用封闭types的一个更引人注目的值。 我强烈希望编译时间的警告/错误,以表明我有代码中的差距/问题,而不是必须从生产运行时exception/故障中收集。


在这方面,我着手研究case objectpath,看看我能否提出一个解决scheme,涵盖了上面列举的所有定义。 第一个挑战是推动JVM类/对象初始化问题的核心(在这个StackOverflow文章中详细介绍)。 而我终于能够找出一个解决scheme。

由于我的解决办法是两个特点, 枚举和枚举修饰 ,因为Enumeration特性超过+400行(大量解释上下文的注释),所以我放弃将它粘贴到这个线程中(这可以使它在页面上可见)。 详情请直接跳到Gist 。

这里的解决scheme最终看起来像使用上面的相同的数据想法(完整评论版本在这里可用 ),并在EnumerationDecorated实现。

 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 } 

这是我创build的一对新枚举特征(位于此Gist中 )的示例用法,用于实现枚举定义中所需的所有function。

expression的一个担心是枚举成员名称必须重复(上例中的decorationOrderedSet )。 虽然我确实把它缩小到了一个单一的重复,但由于两个问题,我看不出如何减less这个问题:

  1. 这个特定的对象/案例对象模型的JVM对象/类初始化是未定义的(请参阅此Stackoverflow线程 )
  2. 从方法getClass.getDeclaredClasses返回的内容具有未定义的顺序(并且与源代码中的case object声明的顺序不太一样)

鉴于这两个问题,我不得不放弃试图产生一个隐含的顺序,并不得不明确要求客户定义和声明某种有序集的概念。 由于Scala集合没有插入有序集的实现,所以最好的做法是使用List ,然后运行时检查它是否是一个集合。 这不是我最喜欢做到的。

考虑到devise要求第二个列表/设置顺序val ,给定上面的ChessPiecesEnhancedDecorated示例,可以添加case object PAWN2 extends Member ,然后忘记添加Decoration(PAWN2,'P2', 2)decorationOrderedSet 。 因此,有一个运行时检查来validation列表不仅是一个集合,而是包含所有扩展sealed trait Member的个案对象。 这是反思/macros观地狱的一种特殊forms。

请在Gist上留下意见和/或反馈意见。

在枚举上使用case类的优点是:

  • 当使用密封大小写类时,Scala编译器可以判断匹配是否完全指定,例如匹配声明中所有可能的匹配。 用枚举,Scala编译器不能告诉。
  • Case类自然支持比支持名称和ID的基于值的枚举更多的字段。

使用枚举而不是案例类的优点是:

  • 枚举通常会less一点代码来编写。
  • 对于新来Scala的人来说,枚举更容易理解,因为他们在其他语言中很普遍

所以一般来说,如果你只需要一个名字简单的常量列表,使用枚举。 否则,如果你需要一些更复杂的东西,或者希望编译器提供额外的安全性,告诉你是否指定了所有的匹配项,则使用大小写类。

更新:下面的代码有一个错误, 在这里描述。 下面的testing程序可以正常工作,但是如果您在DayOfWeek本身之前使用了DayOfWeek.Mon,则会失败,因为DayOfWeek尚未初始化(使用内部对象不会导致外部对象被初始化)。 如果在主类中执行val enums = Seq( DayOfWeek )类的操作,则可以使用此代码,强制枚举初始化,也可以使用chaotic3quilibrium的修改。 期待一个基于macros的枚举!


如果你想

  • 有关非详尽模式匹配的警告
  • 分配给每个枚举值的Int ID,您可以select控制它
  • 枚举值的不可变列表,按照它们定义的顺序
  • 从名称到枚举值的不可变映射
  • 从id到枚举值的不可变映射
  • 所有或特定的枚举值的方法/数据的位置,或枚举作为一个整体
  • 命令枚举值(所以你可以testing,例如,是否在星期三)
  • 扩展一个枚举来创build其他的能力

那么以下可能是有趣的。 反馈欢迎。

在这个实现中,有扩展的抽象Enum和EnumVal基类。 我们将在一分钟内看到这些类,但首先,您将如何定义一个枚举:

 object DayOfWeek extends Enum { sealed abstract class Val extends EnumVal case object Mon extends Val; Mon() case object Tue extends Val; Tue() case object Wed extends Val; Wed() case object Thu extends Val; Thu() case object Fri extends Val; Fri() case object Sat extends Val; Sat() case object Sun extends Val; Sun() } 

请注意,您必须使用每个枚举值(调用其apply方法)才能使其生效。 [我希望内心的东西不要懒惰,除非我专门要求他们。 我认为。]

我们当然可以将方法/数据添加到DayOfWeek,Val或个别的案例对象(如果我们希望的话)。

以下是你将如何使用这样一个枚举:

 object DayOfWeekTest extends App { // To get a map from Int id to enum: println( DayOfWeek.valuesById ) // To get a map from String name to enum: println( DayOfWeek.valuesByName ) // To iterate through a list of the enum values in definition order, // which can be made different from ID order, and get their IDs and names: DayOfWeek.values foreach { v => println( v.id + " = " + v ) } // To sort by ID or name: println( DayOfWeek.values.sorted mkString ", " ) println( DayOfWeek.values.sortBy(_.toString) mkString ", " ) // To look up enum values by name: println( DayOfWeek("Tue") ) // Some[DayOfWeek.Val] println( DayOfWeek("Xyz") ) // None // To look up enum values by id: println( DayOfWeek(3) ) // Some[DayOfWeek.Val] println( DayOfWeek(9) ) // None import DayOfWeek._ // To compare enums as ordinals: println( Tue < Fri ) // Warnings about non-exhaustive pattern matches: def aufDeutsch( day: DayOfWeek.Val ) = day match { case Mon => "Montag" case Tue => "Dienstag" case Wed => "Mittwoch" case Thu => "Donnerstag" case Fri => "Freitag" // Commenting these out causes compiler warning: "match is not exhaustive!" // case Sat => "Samstag" // case Sun => "Sonntag" } } 

这是你编译时得到的结果:

 DayOfWeekTest.scala:31: warning: match is not exhaustive! missing combination Sat missing combination Sun def aufDeutsch( day: DayOfWeek.Val ) = day match { ^ one warning found 

你可以用“(day:@unchecked)match”代替“day match”,你不想要这样的警告,或者只是在最后包括一个全面的情况。

当你运行上面的程序时,你会得到这个输出:

 Map(0 -> Mon, 5 -> Sat, 1 -> Tue, 6 -> Sun, 2 -> Wed, 3 -> Thu, 4 -> Fri) Map(Thu -> Thu, Sat -> Sat, Tue -> Tue, Sun -> Sun, Mon -> Mon, Wed -> Wed, Fri -> Fri) 0 = Mon 1 = Tue 2 = Wed 3 = Thu 4 = Fri 5 = Sat 6 = Sun Mon, Tue, Wed, Thu, Fri, Sat, Sun Fri, Mon, Sat, Sun, Thu, Tue, Wed Some(Tue) None Some(Thu) None true 

请注意,由于列表和地图是不可变的,您可以轻松地删除元素来创build子集,而不会中断枚举本身。

这里是Enum类本身(和其中的EnumVal):

 abstract class Enum { type Val <: EnumVal protected var nextId: Int = 0 private var values_ = List[Val]() private var valuesById_ = Map[Int ,Val]() private var valuesByName_ = Map[String,Val]() def values = values_ def valuesById = valuesById_ def valuesByName = valuesByName_ def apply( id : Int ) = valuesById .get(id ) // Some|None def apply( name: String ) = valuesByName.get(name) // Some|None // Base class for enum values; it registers the value with the Enum. protected abstract class EnumVal extends Ordered[Val] { val theVal = this.asInstanceOf[Val] // only extend EnumVal to Val val id = nextId def bumpId { nextId += 1 } def compare( that:Val ) = this.id - that.id def apply() { if ( valuesById_.get(id) != None ) throw new Exception( "cannot init " + this + " enum value twice" ) bumpId values_ ++= List(theVal) valuesById_ += ( id -> theVal ) valuesByName_ += ( toString -> theVal ) } } } 

这是一个更高级的用法,它控制着ID并将数据/方法添加到Val抽象和枚举本身:

 object DayOfWeek extends Enum { sealed abstract class Val( val isWeekday:Boolean = true ) extends EnumVal { def isWeekend = !isWeekday val abbrev = toString take 3 } case object Monday extends Val; Monday() case object Tuesday extends Val; Tuesday() case object Wednesday extends Val; Wednesday() case object Thursday extends Val; Thursday() case object Friday extends Val; Friday() nextId = -2 case object Saturday extends Val(false); Saturday() case object Sunday extends Val(false); Sunday() val (weekDays,weekendDays) = values partition (_.isWeekday) } 

我有一个很好的简单的lib,它允许你使用密码特征/类作为枚举值,而不必维护自己的值列表。 它依赖于一个简单的macros,它不依赖于knownDirectSubclasses

https://github.com/lloydmeta/enumeratum

案例类与枚举的另一个缺点是,当您需要对所有实例进行迭代或过滤时。 这是枚举(以及Java枚举)的内置function,而案例类不会自动支持这种function。

换句话说:“没有简单的方法来获得与案例类别的所有枚举值列表”。

如果您对保持与其他JVM语言(如Java)的互操作性非常认真,那么最好的select是编写Java枚举。 那些从Scala和Java代码透明地工作,这是可以说scala.Enumeration或case对象。 如果可以避免的话,我们不要为GitHub上的每一个新的兴趣项目都有一个新的枚举库。

我见过使用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) } } 

它允许您构build如下所示的案例类:

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

也许有人可以想出一个更好的伎俩,而不是像我一样简单地将每个案例类添加到列表中。 这是我当时所能想到的。

2017年3月更新:由Anthony scala.Enumeration/enum评论, scala.Enumeration/enum PR已经closures。

Dotty (Scala的下一代编译器)将率先发行 ,尽pipe1970年的dotty问题和Martin Odersky的1958年的PR 。


注意:现在(2016年8月,6年以上)提议删除scala.Enumeration : PR 5352

弃用scala.Enumeration ,添加@enum注释

语法

 @enum class Toggle { ON OFF } 

是一个可能的实现例子,意图是也支持符合某些限制(不嵌套,recursion或不同的构造函数参数)的ADT,例如:

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

scala.Enumeration的无法解决的灾难。

@enum优于scala的优点。枚举:

  • 其实工作
  • Java互操作
  • 没有删除问题
  • 在定义枚举时不会迷惑迷你DSL

缺点:没有。

这解决了无法拥有一个支持Scala-JVM, Scala.js和Scala-Native的代码库的问题( Scala.js/Scala-Native不支持Java源代码,Scala源代码无法定义枚举被Scala-JVM上的现有API所接受)。

在过去几次我需要他们的时候,我一直在这两个选项上来回奔波。 直到最近,我的select是密封的特质/案例对象选项。

1)Scala枚举声明

 object OutboundMarketMakerEntryPointType extends Enumeration { type OutboundMarketMakerEntryPointType = Value val Alpha, Beta = Value } 

2)密封性状+案例对象

 sealed trait OutboundMarketMakerEntryPointType case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType case object BetaEntryPoint extends OutboundMarketMakerEntryPointType 

虽然这些都没有真正满足Java枚举给你所有的东西,下面是利弊:

Scala枚举

优点: – 使用选项实例化或直接假定准确的function(从持久性存储加载时更容易) – 支持对所有可能的值的支持

缺点: – 不支持非详尽search的编译警告(使模式匹配不太理想)

案例对象/密封特征

优点: – 使用密码特征,我们可以预先实例化一些值,而其他值可以在创build时注入 – 完全支持模式匹配(定义了应用/不应用的方法)

缺点: – 从一个持久性存储区进行实例化 – 您经常需要在这里使用模式匹配或者定义您自己的所有可能的“枚举值”列表

最终让我改变了我的看法的东西就像下面的代码片段:

 object DbInstrumentQueries { def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = { val symbol = rs.getString(tableAlias + ".name") val quoteCurrency = rs.getString(tableAlias + ".quote_currency") val fixRepresentation = rs.getString(tableAlias + ".fix_representation") val pointsValue = rs.getInt(tableAlias + ".points_value") val instrumentType = InstrumentType.fromString(rs.getString(tableAlias +".instrument_type")) val productType = ProductType.fromString(rs.getString(tableAlias + ".product_type")) Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType) } } object InstrumentType { def fromString(instrumentType: String): InstrumentType = Seq(CurrencyPair, Metal, CFD) .find(_.toString == instrumentType).get } object ProductType { def fromString(productType: String): ProductType = Seq(Commodity, Currency, Index) .find(_.toString == productType).get } 

The .get calls were hideous – using enumeration instead I can simply call the withName method on the enumeration as follows:

 object DbInstrumentQueries { def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = { val symbol = rs.getString(tableAlias + ".name") val quoteCurrency = rs.getString(tableAlias + ".quote_currency") val fixRepresentation = rs.getString(tableAlias + ".fix_representation") val pointsValue = rs.getInt(tableAlias + ".points_value") val instrumentType = InstrumentType.withNameString(rs.getString(tableAlias + ".instrument_type")) val productType = ProductType.withName(rs.getString(tableAlias + ".product_type")) Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType) } } 

So I think my preference going forward is to use Enumerations when the values are intended to be accessed from a repository and case objects/sealed traits otherwise.

I prefer case objects (it's a matter of personal preference). To cope with the problems inherent to that approach (parse string and iterate over all elements), I've added a few lines that are not perfect, but are effective.

I'm pasting you the code here expecting it could be useful, and also that others could improve it.

 /** * Enum for Genre. It contains the type, objects, elements set and parse method. * * This approach supports: * * - Pattern matching * - Parse from name * - Get all elements */ object Genre { sealed trait Genre case object MALE extends Genre case object FEMALE extends Genre val elements = Set (MALE, FEMALE) // You have to take care this set matches all objects def apply (code: String) = if (MALE.toString == code) MALE else if (FEMALE.toString == code) FEMALE else throw new IllegalArgumentException } /** * Enum usage (and tests). */ object GenreTest extends App { import Genre._ val m1 = MALE val m2 = Genre ("MALE") assert (m1 == m2) assert (m1.toString == "MALE") val f1 = FEMALE val f2 = Genre ("FEMALE") assert (f1 == f2) assert (f1.toString == "FEMALE") try { Genre (null) assert (false) } catch { case e: IllegalArgumentException => assert (true) } try { Genre ("male") assert (false) } catch { case e: IllegalArgumentException => assert (true) } Genre.elements.foreach { println } } 

For those still looking how to get GatesDa's answer to work : You can just reference the case object after declaring it to instantiate it:

 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 }