是否有关于何时使用 case classes(或案例对象)与在 Scala 中扩展 Enumeration 的最佳实践指南?
它们似乎提供了一些相同的好处。
enum
(2020 年中)。
一个很大的区别是 Enumeration
支持从一些 name
字符串实例化它们。例如:
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
然后你可以这样做:
val ccy = Currency.withName("EUR")
当希望持久化枚举(例如,到数据库)或从文件中的数据创建枚举时,这很有用。但是,总的来说,我发现枚举在 Scala 中有点笨拙,并且给人一种笨拙的附加组件的感觉,因此我现在倾向于使用 case object
。 case object
比枚举更灵活:
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)”模式,除了“打破”货币类型的封闭集性质之外,还有其他方法可以处理找不到货币代码字符串。属于 Currency 类型的 UnknownCurrency 现在可以潜入 API 的其他部分。建议将该案例推到 Enumeration 之外,并让客户端处理一个 Option[Currency] 类型,这将清楚地表明确实存在匹配问题,并“鼓励”API 的用户自己解决问题。
要跟进此处的其他答案,case object
优于 Enumeration
的主要缺点是:
无法遍历“枚举”的所有实例。情况确实如此,但我发现在实践中很少需要这样做。无法从持久值轻松实例化。这也是正确的,但除了大量枚举(例如,所有货币)的情况外,这不会产生巨大的开销。
更新:已创建一个新的 macro based solution,它远远优于我在下面概述的解决方案。我强烈推荐使用这个新的 macro based solution。 And it appears plans for Dotty will make this style of enum solution part of the language. 哇哦!
总结:
尝试在 Scala 项目中重现 Java Enum
有三种基本模式。三种模式中的两种;直接使用 Java Enum
和 scala.Enumeration
,无法启用 Scala 的详尽模式匹配。第三个; “密封特征 + 案例对象”,确实......但有 JVM class/object initialization complications 导致不一致的序数索引生成。
我创建了一个包含两个类的解决方案; Enumeration 和 EnumerationDecorated,位于此 Gist。我没有将代码发布到这个线程中,因为 Enumeration 的文件非常大(+400 行 - 包含许多解释实现上下文的注释)。
详情:
你问的问题很笼统; “...何时使用 case
classesobjects
与扩展 [scala.]Enumeration
”。事实证明,有很多可能的答案,每个答案都取决于您所拥有的特定项目要求的微妙之处。答案可以简化为三种基本模式。
首先,让我们确保我们的工作与枚举的基本概念相同。让我们主要根据 Enum
provided as of Java 5 (1.5) 来定义一个枚举:
它包含一个自然有序的封闭命名成员集 成员数量固定可以根据索引轻松遍历成员 可以使用其(区分大小写的)名称检索成员 如果还可以使用不区分大小写的名称检索成员,那将是非常好的 可以使用其索引检索成员 成员可能很容易,透明且高效地使用序列化 成员可以很容易地扩展以保存额外的关联单例数据 考虑到 Java 的枚举之外,能够显式地利用 Scala 的模式匹配穷举检查来进行枚举会很好
接下来,让我们看一下发布的三种最常见的解决方案模式的简化版本:
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;
}
}
枚举定义中的以下项目不可用:
3.1 - 如果成员也可以用其不区分大小写的名称来检索,那就太好了 7 - 超越 Java 的 Enum 思考,能够显式地利用 Scala 的模式匹配穷举检查来进行枚举会很好
对于我目前的项目,我没有在 Scala/Java 混合项目路径上冒险的好处。即使我可以选择做一个混合项目,如果/当我添加/删除枚举成员,或者正在编写一些新代码来处理现有枚举成员时,第 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.2 - 成员自然排序并显式索引 2 - 所有成员都可以根据其索引轻松迭代 3 - 可以使用其(区分大小写)名称检索成员 3.1 - 如果还可以检索成员,那就太好了名称不区分大小写 4 - 可以使用索引检索成员
可以说它确实符合枚举定义项 5 和 6。对于 5,声称它是有效的有点牵强。对于 6,扩展以保存额外的关联单例数据并不容易。
C) 使用 scala.Enumeration
模式 (受 this StackOverflow answer 启发):
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 枚举的列表相同):
3.1 - 如果一个成员也可以用其不区分大小写的名称来检索,那就太好了 7 - 超越 Java 的 Enum 思考,能够显式地利用 Scala 的模式匹配穷举检查来进行枚举会很好
同样对于我当前的项目,如果/当我添加/删除枚举成员或正在编写一些新代码来处理现有枚举成员时,第 7 项对于让我捕捉编译时问题至关重要。
因此,鉴于上述枚举定义,上述三种解决方案均不起作用,因为它们没有提供上述枚举定义中概述的所有内容:
Java Enum 直接在混合 Scala/Java 项目“密封特征 + 案例对象”中 scala.Enumeration
这些解决方案中的每一个最终都可以重新设计/扩展/重构,以尝试覆盖每个解决方案的一些缺失要求。但是,Java Enum
和 scala.Enumeration
解决方案都不能充分扩展以提供第 7 项。对于我自己的项目,这是在 Scala 中使用封闭类型的更引人注目的价值之一。我非常喜欢编译时警告/错误来表明我的代码中存在差距/问题,而不是必须从生产运行时异常/故障中收集它。
在这方面,我开始使用 case object
路径,看看我是否可以产生一个涵盖上述所有枚举定义的解决方案。第一个挑战是突破 JVM 类/对象初始化问题的核心(在 this StackOverflow post 中有详细介绍)。我终于能够想出一个解决方案。
因为我的解决方案是两个特征; Enumeration 和 EnumerationDecorated,并且由于 Enumeration
特征超过 +400 行(很多解释上下文的评论),我放弃将它粘贴到这个线程中(这会使它在页面上显着延伸)。详情请直接跳至Gist。
以下是使用与上述相同的数据理念(完全注释版本 available here)并在 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
}
这是我创建的一对新枚举特征(位于 this Gist 中)的示例用法,用于实现枚举定义中所需和概述的所有功能。
表达的一种担忧是枚举成员名称必须重复(上面示例中的 decorationOrderedSet
)。虽然我确实将其最小化为一次重复,但由于两个问题,我不知道如何使其更少:
此特定对象/案例对象模型的 JVM 对象/类初始化未定义(请参阅此 Stackoverflow 线程)从方法 getClass.getDeclaredClasses 返回的内容具有未定义的顺序(并且不太可能与案例对象的顺序相同源代码中的声明)
鉴于这两个问题,我不得不放弃尝试生成隐含排序,而必须明确要求客户端使用某种有序集合概念定义和声明它。由于 Scala 集合没有插入有序集合实现,我能做的最好的事情就是使用 List
,然后运行时检查它是否真的是集合。这不是我希望实现这一目标的方式。
鉴于设计需要第二个列表/集合排序 val
,鉴于上面的 ChessPiecesEnhancedDecorated
示例,可以添加 case object PAWN2 extends Member
,然后忘记将 Decoration(PAWN2,'P2', 2)
添加到 decorationOrderedSet
。因此,有一个运行时检查来验证列表不仅是一个集合,而且包含所有扩展 sealed trait Member
的案例对象。这是一种特殊形式的反射/宏观地狱。
请在 Gist 上留下评论和/或反馈。
org.scalaolio.util.Enumeration
和 org.scalaolio.util.EnumerationDecorated
的更多最新版本:scalaolio.org
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") {}
}
如果您不介意作弊,您可以使用反射 API 或 Google Reflections 之类的工具预加载枚举值。非惰性案例对象为您提供最简洁的语法:
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
Currency.values
,我只会取回我之前访问过的值。有什么办法吗?
使用案例类优于枚举的优点是:
当使用密封案例类时,Scala 编译器可以判断匹配是否完全指定,例如,当匹配声明中支持所有可能的匹配时。使用枚举,Scala 编译器无法分辨。
与支持名称和 ID 的基于值的枚举相比,案例类自然支持更多字段。
使用枚举而不是案例类的优点是:
枚举通常会少写一些代码。
枚举对于刚接触 Scala 的人来说更容易理解,因为它们在其他语言中很流行
因此,一般来说,如果您只需要按名称列出的简单常量列表,请使用枚举。否则,如果您需要一些更复杂的东西或希望编译器的额外安全性告诉您是否指定了所有匹配项,请使用用例类。
更新:下面的代码有一个错误,描述为 here。下面的测试程序有效,但如果您在 DayOfWeek 本身之前使用 DayOfWeek.Mon(例如),它将失败,因为 DayOfWeek 尚未初始化(使用内部对象不会导致外部对象被初始化)。如果您在主类中执行 val enums = Seq( DayOfWeek )
之类的操作,强制初始化枚举,您仍然可以使用此代码,或者您可以使用 chaotic3quilibrium 的修改。期待基于宏的枚举!
如果你想
关于非详尽模式匹配的警告
分配给每个枚举值的 Int ID,您可以选择控制它
枚举值的不可变列表,按定义的顺序排列
从名称到枚举值的不可变映射
从 id 到枚举值的不可变 Map
为所有或特定枚举值或整个枚举粘贴方法/数据的地方
有序枚举值(因此您可以测试,例如,是否天 < 星期三)
扩展一个枚举以创建其他枚举的能力
那么以下内容可能会引起您的兴趣。欢迎反馈。
在此实现中,您可以扩展抽象 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 match”替换为“(day: @unchecked) 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
请注意,由于 List 和 Maps 是不可变的,您可以轻松删除元素以创建子集,而不会破坏枚举本身。
这是 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)
}
var
] 在 FP 世界中是一种致命的罪过”——我认为这种观点并未被普遍接受。
我在这里有一个很好的简单库,它允许您使用密封的特征/类作为枚举值,而无需维护自己的值列表。它依赖于一个不依赖于错误 knownDirectSubclasses
的简单宏。
https://github.com/lloydmeta/enumeratum
2017 年 3 月更新:如 Anthony Accioly 所述,scala.Enumeration/enum
PR 已关闭。
Dotty(Scala 的下一代编译器)将处于领先地位,不过 dotty issue 1970 和 Martin Odersky's PR 1958。
注意:现在(2016 年 8 月,6 年多后)提出了删除 scala.Enumeration
的提议:PR 5352
弃用 scala.Enumeration,添加 @enum 注解语法
@enum
class Toggle {
ON
OFF
}
是一个可能的实现示例,目的是还支持符合某些限制(无嵌套、递归或可变构造函数参数)的 ADT,例如:
@enum
sealed trait Toggle
case object ON extends Toggle
case object OFF extends Toggle
弃用 scala.Enumeration 的彻底灾难。 @enum 优于 scala.Enumeration 的优点:实际上可以工作 Java 互操作 没有擦除问题 定义枚举时不会混淆 mini-DSL 缺点:无。这解决了无法拥有一个支持 Scala-JVM、Scala.js 和 Scala-Native 的代码库的问题(Scala.js/Scala-Native 不支持 Java 源代码,Scala 源代码无法定义枚举) Scala-JVM 上的现有 API 接受)。
当您需要迭代或过滤所有实例时,案例类与枚举的另一个缺点。这是枚举(以及 Java 枚举)的内置功能,而案例类不会自动支持这种功能。
换句话说:“没有简单的方法来获得带有案例类的枚举值的总集列表”。
如果您认真维护与其他 JVM 语言(例如 Java)的互操作性,那么最好的选择是编写 Java 枚举。这些从 Scala 和 Java 代码透明地工作,对于 scala.Enumeration
或案例对象来说,这不仅仅是可以说的。如果可以避免的话,我们不要为 GitHub 上的每个新爱好项目都创建一个新的枚举库!
我见过使案例类模仿枚举的各种版本。这是我的版本:
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)
}
}
它允许您构造如下所示的案例类:
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)
}
也许有人可以想出一个更好的技巧,而不是像我一样简单地将每个案例类添加到列表中。这就是我当时所能想到的。
我更喜欢 case objects
(这是个人喜好问题)。为了解决该方法固有的问题(解析字符串并遍历所有元素),我添加了一些不完美但有效的行。
我将代码粘贴在这里,希望它可能有用,并且其他人可以改进它。
/**
* 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 }
}
在我需要它们的最后几次,我一直在这两个选项上来回走动。直到最近,我的偏好一直是sealed trait/case object 选项。
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 枚举给你的所有东西,但以下是优缺点:
斯卡拉枚举
优点: - 使用选项实例化或直接假设准确的功能(从持久存储加载时更容易) - 支持对所有可能值的迭代
缺点: - 不支持非详尽搜索的编译警告(使模式匹配不太理想)
案例对象/密封特征
优点: - 使用密封特征,我们可以预先实例化一些值,而其他值可以在创建时注入 - 完全支持模式匹配(定义应用/取消应用方法)
缺点: - 从持久存储中实例化 - 您经常必须在此处使用模式匹配或定义您自己的所有可能的“枚举值”列表
最终让我改变看法的是以下片段:
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
}
.get
调用是可怕的 - 使用枚举代替我可以简单地调用枚举的 withName 方法,如下所示:
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)
}
}
所以我认为我的偏好是在打算从存储库访问值时使用枚举,否则使用案例对象/密封特征。
对于那些仍在寻找如何获取 GatesDa's answer to work 的人:您可以在声明实例对象后引用它来实例化它:
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 classes
优于 enumerations
的最大优势在于您可以使用 type class pattern aka ad-hoc polymorphysm。不需要匹配枚举,例如:
someEnum match {
ENUMA => makeThis()
ENUMB => makeThat()
}
相反,你会得到类似的东西:
def someCode[SomeCaseClass](implicit val maker: Maker[SomeCaseClass]){
maker.make()
}
implicit val makerA = new Maker[CaseClassA]{
def make() = ...
}
implicit val makerB = new Maker[CaseClassB]{
def make() = ...
}
trade.ccy
的项目的类型。case
object
生成的代码占用量不会比Enumeration
大(~4 倍)吗?有用的区别,特别是对于需要占用空间小的scala.js
项目。