scala 如何建模类型安全的枚举类型?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1321745/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to model type-safe enum types?
提问by Jesper
Scala doesn't have type-safe enums like Java has. Given a set of related constants, what would be the best way in Scala to represent those constants?
Scala 没有enum像 Java 那样的类型安全。给定一组相关的常量,Scala 中表示这些常量的最佳方式是什么?
采纳答案by skaffman
http://www.scala-lang.org/docu/files/api/scala/Enumeration.html
http://www.scala-lang.org/docu/files/api/scala/Enumeration.html
Example use
示例使用
object Main extends App {
object WeekDay extends Enumeration {
type WeekDay = Value
val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
}
import WeekDay._
def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)
WeekDay.values filter isWorkingDay foreach println
}
回答by oxbow_lakes
I must say that the example copied out of the Scala documentationby skaffmanabove is of limited utility in practice (you might as well use case objects).
我必须说,上面由skaffman从 Scala 文档中复制的示例在实践中的实用性有限(您不妨使用s)。case object
In order to get something most closely resembling a Java Enum(i.e. with sensible toStringand valueOfmethods -- perhaps you are persisting the enum values to a database) you need to modify it a bit. If you had used skaffman's code:
为了得到一些东西最接近类似的一个Java Enum(与理智即toString与valueOf方法-也许你坚持枚举值到数据库),你需要稍作修改。如果您使用过skaffman的代码:
WeekDay.valueOf("Sun") //returns None
WeekDay.Tue.toString //returns Weekday(2)
Whereas using the following declaration:
而使用以下声明:
object WeekDay extends Enumeration {
type WeekDay = Value
val Mon = Value("Mon")
val Tue = Value("Tue")
... etc
}
You get more sensible results:
你会得到更合理的结果:
WeekDay.valueOf("Sun") //returns Some(Sun)
WeekDay.Tue.toString //returns Tue
回答by Daniel C. Sobral
There are many ways of doing.
有很多方法可以做。
1) Use symbols. It won't give you any type safety, though, aside from not accepting non-symbols where a symbol is expected. I'm only mentioning it here for completeness. Here's an example of usage:
1) 使用符号。但是,除了不接受需要符号的非符号之外,它不会为您提供任何类型安全性。我在这里提到它只是为了完整性。下面是一个使用示例:
def update(what: Symbol, where: Int, newValue: Array[Int]): MatrixInt =
what match {
case 'row => replaceRow(where, newValue)
case 'col | 'column => replaceCol(where, newValue)
case _ => throw new IllegalArgumentException
}
// At REPL:
scala> val a = unitMatrixInt(3)
a: teste7.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 0 1 /
scala> a('row, 1) = a.row(0)
res41: teste7.MatrixInt =
/ 1 0 0 \
| 1 0 0 |
\ 0 0 1 /
scala> a('column, 2) = a.row(0)
res42: teste7.MatrixInt =
/ 1 0 1 \
| 0 1 0 |
\ 0 0 0 /
2) Using class Enumeration:
2)使用类Enumeration:
object Dimension extends Enumeration {
type Dimension = Value
val Row, Column = Value
}
or, if you need to serialize or display it:
或者,如果您需要序列化或显示它:
object Dimension extends Enumeration("Row", "Column") {
type Dimension = Value
val Row, Column = Value
}
This can be used like this:
这可以像这样使用:
def update(what: Dimension, where: Int, newValue: Array[Int]): MatrixInt =
what match {
case Row => replaceRow(where, newValue)
case Column => replaceCol(where, newValue)
}
// At REPL:
scala> a(Row, 2) = a.row(1)
<console>:13: error: not found: value Row
a(Row, 2) = a.row(1)
^
scala> a(Dimension.Row, 2) = a.row(1)
res1: teste.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 1 0 /
scala> import Dimension._
import Dimension._
scala> a(Row, 2) = a.row(1)
res2: teste.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 1 0 /
Unfortunately, it doesn't ensure that all matches are accounted for. If I forgot to put Row or Column in the match, the Scala compiler wouldn't have warned me. So it gives me sometype safety, but not as much as can be gained.
不幸的是,它并不能确保所有匹配项都被考虑在内。如果我忘记将 Row 或 Column 放在匹配中,Scala 编译器不会警告我。所以它给了我一些类型安全,但没有获得那么多。
3) Case objects:
3) 案例对象:
sealed abstract class Dimension
case object Row extends Dimension
case object Column extends Dimension
Now, if I leave out a case on a match, the compiler will warn me:
现在,如果我在 a 上遗漏了一个案例match,编译器会警告我:
MatrixInt.scala:70: warning: match is not exhaustive!
missing combination Column
what match {
^
one warning found
It's used pretty much the same way, and doesn't even need an import:
它的使用方式几乎相同,甚至不需要import:
scala> val a = unitMatrixInt(3)
a: teste3.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 0 0 1 /
scala> a(Row,2) = a.row(0)
res15: teste3.MatrixInt =
/ 1 0 0 \
| 0 1 0 |
\ 1 0 0 /
You might wonder, then, why ever use an Enumeration instead of case objects. As a matter of fact, case objects do have advantages many times, such as here. The Enumeration class, though, has many Collection methods, such as elements (iterator on Scala 2.8), which returns an Iterator, map, flatMap, filter, etc.
那么,您可能想知道为什么要使用 Enumeration 而不是 case 对象。事实上,case 对象确实有很多时候的优势,比如这里。然而,Enumeration 类有许多 Collection 方法,例如元素(Scala 2.8 上的迭代器),它返回一个迭代器、映射、平面映射、过滤器等。
This answer is essentially a selected parts from this articlein my blog.
这个答案本质上是我博客中这篇文章的精选部分。
回答by Walter Chang
A slightly less verbose way of declaring named enumerations:
一种稍微不那么冗长的声明命名枚举的方式:
object WeekDay extends Enumeration("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat") {
type WeekDay = Value
val Sun, Mon, Tue, Wed, Thu, Fri, Sat = Value
}
WeekDay.valueOf("Wed") // returns Some(Wed)
WeekDay.Fri.toString // returns Fri
Of course the problem here is that you will need to keep the ordering of the names and vals in sync which is easier to do if name and val are declared on the same line.
当然,这里的问题是您需要保持名称和 val 的顺序同步,如果 name 和 val 在同一行上声明,这会更容易做到。
回答by ron
You can use a sealed abstract class instead of the enumeration, for example:
您可以使用密封的抽象类代替枚举,例如:
sealed abstract class Constraint(val name: String, val verifier: Int => Boolean)
case object NotTooBig extends Constraint("NotTooBig", (_ < 1000))
case object NonZero extends Constraint("NonZero", (_ != 0))
case class NotEquals(x: Int) extends Constraint("NotEquals " + x, (_ != x))
object Main {
def eval(ctrs: Seq[Constraint])(x: Int): Boolean =
(true /: ctrs){ case (accum, ctr) => accum && ctr.verifier(x) }
def main(args: Array[String]) {
val ctrs = NotTooBig :: NotEquals(5) :: Nil
val evaluate = eval(ctrs) _
println(evaluate(3000))
println(evaluate(3))
println(evaluate(5))
}
}
回答by practechal
just discovered enumeratum. it's pretty amazing and equally amazing it's not more well known!
刚刚发现enumeratum。它非常了不起,同样令人惊奇的是它并不为人所知!
回答by chaotic3quilibrium
After doing extensive research on all the options around "enumerations" in Scala, I posted a much more complete overview of this domain on another StackOverflow thread. It includes a solution to the "sealed trait + case object" pattern where I have solved the JVM class/object initialization ordering problem.
在对 Scala 中有关“枚举”的所有选项进行广泛研究后,我在另一个StackOverflow 线程上发布了对该域的更完整概述。它包括“密封特征 + 案例对象”模式的解决方案,我已经解决了 JVM 类/对象初始化排序问题。
回答by zeronone
回答by Dmitriy Kuzkin
In Scala it is very comfortable with https://github.com/lloydmeta/enumeratum
在 Scala 中使用https://github.com/lloydmeta/enumeratum非常舒服
Project is really good with examples and documentation
项目非常好,有示例和文档
Just this example from their docs should makes you interested in
只是他们文档中的这个例子应该让你感兴趣
import enumeratum._
sealed trait Greeting extends EnumEntry
object Greeting extends Enum[Greeting] {
/*
`findValues` is a protected method that invokes a macro to find all `Greeting` object declarations inside an `Enum`
You use it to implement the `val values` member
*/
val values = findValues
case object Hello extends Greeting
case object GoodBye extends Greeting
case object Hi extends Greeting
case object Bye extends Greeting
}
// Object Greeting has a `withName(name: String)` method
Greeting.withName("Hello")
// => res0: Greeting = Hello
Greeting.withName("Haro")
// => java.lang.IllegalArgumentException: Haro is not a member of Enum (Hello, GoodBye, Hi, Bye)
// A safer alternative would be to use `withNameOption(name: String)` method which returns an Option[Greeting]
Greeting.withNameOption("Hello")
// => res1: Option[Greeting] = Some(Hello)
Greeting.withNameOption("Haro")
// => res2: Option[Greeting] = None
// It is also possible to use strings case insensitively
Greeting.withNameInsensitive("HeLLo")
// => res3: Greeting = Hello
Greeting.withNameInsensitiveOption("HeLLo")
// => res4: Option[Greeting] = Some(Hello)
// Uppercase-only strings may also be used
Greeting.withNameUppercaseOnly("HELLO")
// => res5: Greeting = Hello
Greeting.withNameUppercaseOnlyOption("HeLLo")
// => res6: Option[Greeting] = None
// Similarly, lowercase-only strings may also be used
Greeting.withNameLowercaseOnly("hello")
// => res7: Greeting = Hello
Greeting.withNameLowercaseOnlyOption("hello")
// => res8: Option[Greeting] = Some(Hello)

