[ACCEPTED]-How to model type-safe enum types?-enums

Accepted answer
Score: 376

I must say that the example copied out of the Scala documentation by skaffman above is 8 of limited utility in practice (you might 7 as well use case objects).

In order to get something 6 most closely resembling a Java Enum (i.e. with 5 sensible toString and valueOf methods -- perhaps you are 4 persisting the enum values to a database) you 3 need to modify it a bit. If you had used 2 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 1 get more sensible results:

WeekDay.valueOf("Sun") //returns Some(Sun)
WeekDay.Tue.toString   //returns Tue
Score: 189

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
  }

0

Score: 98

There are many ways of doing.

1) Use symbols. It 22 won't give you any type safety, though, aside 21 from not accepting non-symbols where a symbol 20 is expected. I'm only mentioning it here 19 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 18 class Enumeration:

object Dimension extends Enumeration {
  type Dimension = Value
  val Row, Column = Value
}

or, if you need to serialize or display 17 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 16 doesn't ensure that all matches are accounted 15 for. If I forgot to put Row or Column in 14 the match, the Scala compiler wouldn't have 13 warned me. So it gives me some type safety, but 12 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 11 I leave out a case on a match, the compiler will 10 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 9 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 8 ever use an Enumeration instead of case 7 objects. As a matter of fact, case objects 6 do have advantages many times, such as here. The 5 Enumeration class, though, has many Collection 4 methods, such as elements (iterator on Scala 3 2.8), which returns an Iterator, map, flatMap, filter, etc.

This 2 answer is essentially a selected parts from 1 this article in my blog.

Score: 53

A slightly less verbose way of declaring 5 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 4 here is that you will need to keep the ordering 3 of the names and vals in sync which is easier 2 to do if name and val are declared on the 1 same line.

Score: 18

You can use a sealed abstract class instead 1 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))
  }

}
Score: 6

just discovered enumeratum. it's pretty amazing and 1 equally amazing it's not more well known!

Score: 2

After doing extensive research on all the 7 options around "enumerations" in 6 Scala, I posted a much more complete overview 5 of this domain on another StackOverflow thread. It includes 4 a solution to the "sealed trait + case 3 object" pattern where I have solved 2 the JVM class/object initialization ordering 1 problem.

Score: 2

Starting from Scala 3, there is now enum keyword which 2 can represent a set of constants (and other 1 use cases)

enum Color:
   case Red, Green, Blue

scala> val red = Color.Red
val red: Color = Red
scala> red.ordinal
val res0: Int = 0
Score: 1

In Scala it is very comfortable with https://github.com/lloydmeta/enumeratum

Project 3 is really good with examples and documentation

Just 2 this example from their docs should makes 1 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)
Score: 0

Dotty (Scala 3) will have native enums supported. Check 1 here and here.

More Related questions