Scala doesn't have type-safe enum
s like Java has. Given a set of related constants, what would be the best way in Scala to represent those constants?
10 Answers
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
}
I must say that the example copied out of the Scala documentation by skaffman above is of limited utility in practice (you might as well use case object
s).
In order to get something most closely resembling a Java Enum
(i.e. with sensible toString
and valueOf
methods -- perhaps you are persisting the enum values to a database) you need to modify it a bit. If you had used skaffman's code:
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
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:
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
:
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 some type safety, but not as much as can be gained.
3) Case objects:
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:
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
:
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.
This answer is essentially a selected parts from this article in my blog.
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.
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))
}
}
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.
In Scala it is very comfortable with 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)
Starting from Scala 3, there is now enum keyword which can represent a set of constants (and other use cases)
enum Color:
case Red, Green, Blue
scala> val red = Color.Red
val red: Color = Red
scala> red.ordinal
val res0: Int = 0