40

In Java you could:

public enum Enum {
    ONE {
        public String method() {
            return "1";
        }
    },
    TWO {
        public String method() {
            return "2";
        }
    },
    THREE {
        public String method() {
            return "3";
        }
    };

    public abstract String method();
}

How do you do this in Scala?

EDIT / Useful links:

Etam
  • 4,553
  • 10
  • 40
  • 60
  • 1
    Not an answer to your question but have you considered using case objects instead of Enumerations as discussed here: http://stackoverflow.com/questions/1898932/case-classes-vs-enumerations-in-scala – Ross Dec 03 '10 at 15:34
  • 1
    I've already answered this question at (with several options) at http://stackoverflow.com/questions/4235250/scala-enumerations-with-singleton-objects-as-enumeration-elements-and-a-possibili – Ken Bloom Dec 03 '10 at 20:05
  • After doing extensive research on options, I posted a much more complete overview of this domain including a general solution to the "sealed trait + case object" pattern where I have solved the JVM class/object initialization ordering problem: http://stackoverflow.com/a/25923651/501113 – chaotic3quilibrium Sep 18 '14 at 23:06

12 Answers12

40

Here is an example of adding attributes to scala enums by extending the Enumeration.Val class.

object Planet extends Enumeration { 
   protected case class Val(val mass: Double, val radius: Double) extends super.Val { 
     def surfaceGravity: Double = Planet.G * mass / (radius * radius) 
     def surfaceWeight(otherMass: Double): Double = otherMass * surfaceGravity 
   } 
   implicit def valueToPlanetVal(x: Value) = x.asInstanceOf[Val] 

   val G: Double = 6.67300E-11 
   val Mercury = Val(3.303e+23, 2.4397e6) 
   val Venus   = Val(4.869e+24, 6.0518e6) 
   val Earth   = Val(5.976e+24, 6.37814e6) 
   val Mars    = Val(6.421e+23, 3.3972e6) 
   val Jupiter = Val(1.9e+27, 7.1492e7) 
   val Saturn  = Val(5.688e+26, 6.0268e7) 
   val Uranus  = Val(8.686e+25, 2.5559e7) 
   val Neptune = Val(1.024e+26, 2.4746e7) 
} 

scala> Planet.values.filter(_.radius > 7.0e6) 
res16: Planet.ValueSet = Planet.ValueSet(Jupiter, Saturn, Uranus, Neptune) 
Greg Chabala
  • 1,125
  • 2
  • 23
  • 35
Sean Ross
  • 401
  • 4
  • 2
  • 3
    You can also extend with `super.Val` without the `nextId` and `name` parameter. Like so: `protected case class Val(val mass: Double, val radius: Double) extends super.Val`. That way you can create your values without the name of the value, like so: `val Mercury = Val(3.303e+23, 2.4397e6)` – Jan van der Vorst Jul 06 '11 at 11:59
  • 1
    I do not get what is the advantage of duplicating the name in the constructor argument? Aren't enums supposed to know the names of their values? The official doc says they do http://www.scala-lang.org/api/current/index.html#scala.Enumeration – Val Jul 19 '14 at 08:58
  • Voted to approve the edit, but the answer could be improved by demonstrating how to retrieve the name from the `val` declaration. – Patrick M Oct 24 '14 at 22:15
  • 1
    You will also need [`import scala.language.implicitConversions`](http://docs.scala-lang.org/tutorials/tour/implicit-conversions) to suppress a compiler warning. – 200_success Mar 14 '17 at 18:28
34

Building on Chris' solution, you can achieve somewhat nicer syntax with an implicit conversion:

object Suit extends Enumeration {
   val Clubs, Diamonds, Hearts, Spades = Value

   class SuitValue(suit: Value) {
      def isRed = !isBlack
      def isBlack = suit match {
         case Clubs | Spades => true
         case _              => false
      }
   }

   implicit def value2SuitValue(suit: Value) = new SuitValue(suit)
} 

Then you can call, for example, Suit.Clubs.isRed.

Community
  • 1
  • 1
Aaron Novstrup
  • 20,967
  • 7
  • 70
  • 108
  • 9
    and now you can even use an implicit class instead of class + implicit def: implicit class SuitValue(...) {...} – ozeebee Jan 29 '15 at 14:39
12

Elaborating on Aaron's solution, an even more compact form in Scala 2.10, using implicit classes:

object Suit extends Enumeration {
   val Clubs, Diamonds, Hearts, Spades = Value

   implicit class SuitValue(suit: Value) {
      def isRed = !isBlack
      def isBlack = suit match {
         case Clubs | Spades => true
         case _              => false
      }
   }
} 

and then you can use it like this: Suit.Clubs.isRed

Community
  • 1
  • 1
ozeebee
  • 1,878
  • 2
  • 23
  • 26
12

Scala enumerations are distinct from Java enumerations.

At the moment, there is no way add methods to it (in a sane way). There are some work-arounds, but nothing which works in all cases and doesn't look like syntactic garbage.

I tried something similiar (adding methods to enumerated instance of a class, while being able to create new instances at runtime and having a working equivalence relationship between the objects and the new instances of the class), but was stopped by bug #4023 ("getClasses/getDeclaredClasses seems to miss some (REPL) or all (scalac) classes (objects) declared").

Have a look at these related questions by me:

Honestly, I wouldn't use Enumeration. This is a class originating from Scala 1.0 (2004) and it has weird stuff in it and not many people (except those who have written it) understands how to use it without an tutorial first.

If I would absolutely need an enumeration I would just write that class in Java.

Community
  • 1
  • 1
soc
  • 27,983
  • 20
  • 111
  • 215
  • @axel22: If they would get around fixing bug #4023 I would finally be able to create an `Enum` which combines all benefits of the class/object-based approach with all benefits of the `extends scala.Enumeration` approach ... – soc Dec 03 '10 at 14:54
  • Would it be possible to define an implicit cast from the particular enumeration to a class that contains the method? – andrewmu Dec 03 '10 at 14:56
  • @soc: perhaps the awaited scala reflection library might solve the issues of declared inner classes... – axel22 Dec 03 '10 at 14:57
  • Well, there are some ways which can work around it. But I want to have an DRY approach, because easy things should be easy and not be cluttered with useless boiler plate. – soc Dec 03 '10 at 14:59
  • axel22: Well, I'm not really sure on how much of the Java reflection support it will reuse. (Implementing everything without some help of the JVM will be dog slow and bloat the .class files considerably.) Depending on the level of re-usage `sala.reflect` will have the same problem. IMHO fixing the bug is the obvious solution. – soc Dec 03 '10 at 15:08
  • @soc: you're probably right, I imagine `getDeclaredClasses` should be faster. – axel22 Dec 03 '10 at 15:17
  • 1
    @soc: at least when they write `scala.reflect`, they'll have to test that everything works the way it should. – Ken Bloom Dec 03 '10 at 20:08
  • 1
    @Ken: I hope they fix it earlier than that :-) – soc Dec 03 '10 at 22:30
  • @soc After doing extensive research on options, I posted a much more complete overview of this domain including a solution to the "sealed trait + case object" pattern where I have solved the JVM class/object initialization ordering problem: http://stackoverflow.com/a/25923651/501113 – chaotic3quilibrium Sep 18 '14 at 23:03
11

If you don't need to iterate over enum values or do some other enum-ish stuff, I'd advise using ADTs instead of Enumeration.

sealed abstract class Enum {
  def method: String = this match {
    case One => "1"
    case Two => "2"
    case Three => "3"
  }
}
case object One extends Enum
case object Two extends Enum
case object Three extends Enum

This approach has one advantage over Enumeration that compiler will warn you when you forget one or more cases in the match expression.

missingfaktor
  • 90,905
  • 62
  • 285
  • 365
  • 1
    @mR_fr0g: [Algebraic Data Types](http://en.wikipedia.org/wiki/Algebraic_data_type). – missingfaktor Dec 05 '10 at 16:23
  • While this gives the benefit of providing exhaustive pattern matching, it introduces the possibility of human error by requiring the integer value be explicitly defined. IOW, if someone were to add a case object of Four and then extend the match with case Four => "3", the reverse map won't work as there are now two case objects at the same ordinal value. – chaotic3quilibrium Sep 18 '14 at 23:09
7

You can do this:

object Suit extends Enumeration {
  val Clubs, Diamonds, Hearts, Spades = Value

  def isRed(suit : Value) = !isBlack(suit)
  def isBlack(suit : Value) = suit match {
    case Clubs | Spades => true
    case _              => false
  }
}

Obviously this is not perfect but you can then do:

Suit.isBlack(Suit.Clubs)
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • 2
    Nice! Adding static helper methods to the surrounding class looks like the optimal approach considering the current restrictions. – soc Dec 03 '10 at 15:01
4
object Unit extends Enumeration {
  abstract class UnitValue(var name: String) extends Val(name) {
    def m: Unit
  }
  val G = new UnitValue("g") {
    def m {
        println("M from G")
    }
  }
  val KG = new UnitValue("kg") {
    def m {
        println("M from KG")
    }
  }
}
Etam
  • 4,553
  • 10
  • 40
  • 60
  • Wait, in the Enumeration, you have to write A,B,C = Value, http://stackoverflow.com/questions/11067396. I do not understand your extension of Enumeration. – Val Jul 19 '14 at 08:35
4

Scala's Enumeration does not allow properties and/or methods to be added to values in your enumeration. With this new MyEnumeration you can.

abstract class MyEnumeration {
  // "Value" must be the name of the class defining your values type Value
  type Value

  // Contains your values in definition order
  private val vals = collection.mutable.LinkedHashMap[String, Value]()

  // A mixin for your values class to automatically collect the values
  protected trait ValuesCollector { self: Value =>
    private val ordinal = vals.size

    vals += (fieldNames(ordinal) -> self)

    def getName = fieldNames(ordinal)
    override def toString = getName
  }

  def apply(ordinal: Int) = vals(fieldNames(ordinal))
  def apply(fldName: String) = vals(fldName)

  def values = vals.values
  def namedValues: collection.Map[String, Value] = vals

  // Getting the field names through reflection.
  // Copied from scala.Enumeration
  private val fieldNames = getClass.getMethods filter (m =>
    m.getParameterTypes.isEmpty &&
    classOf[ValuesCollector].isAssignableFrom(m.getReturnType) &&
    m.getDeclaringClass != classOf[MyEnumeration]) map (_.getName)

}

Here you see the Planet example in Scala.

object Planet extends MyEnumeration {

  case class Value(val mass: Double, val radius: Double) extends ValuesCollector {
    // universal gravitational constant  (m3 kg-1 s-2)
    private val G = 6.67300E-11;

    def surfaceGravity = G * mass / (radius * radius)
    def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity

  }

  val MERCURY = Value(3.303e+23, 2.4397e6)
  val VENUS = Value(4.869e+24, 6.0518e6)
  val EARTH = Value(5.976e+24, 6.37814e6)
  val MARS = Value(6.421e+23, 3.3972e6)
  val JUPITER = Value(1.9e+27, 7.1492e7)
  val SATURN = Value(5.688e+26, 6.0268e7)
  val URANUS = Value(8.686e+25, 2.5559e7)
  val NEPTUNE = Value(1.024e+26, 2.4746e7)
  val PLUTO = Value(1.27e+22, 1.137e6)

}

object PlanetTest {
  def main(args: Array[String]) {
    val earthWeight = 175
    val mass = earthWeight/Planet.EARTH.surfaceGravity
    for (p <- Planet.values) println("Your weight on %s is %f" format (p, p.surfaceWeight(mass)))
    /* Your weight on MERCURY is 66.107583
     * Your weight on VENUS is 158.374842
     * Your weight on EARTH is 175.000000
     * Your weight on MARS is 66.279007
     * Your weight on JUPITER is 442.847567
     * Your weight on SATURN is 186.552719
     * Your weight on URANUS is 158.397260
     * Your weight on NEPTUNE is 199.207413
     * Your weight on PLUTO is 11.703031
     */
  }

} 
1

If you absolutely need to have methods per enumeration value and need to be able to iterate over the values, you can do something like this:

object BatchCategory extends Enumeration {
  class BatchCategory extends Val {
    val isOfficial, isTest, isUser = false
  }

  val OFFICIAL = new BatchCategory { override val isOfficial = true }
  val TEST =     new BatchCategory { override val isTest = true }
  val USER =     new BatchCategory { override val isUser = true }

  // Needed to get BatchCategory from Enumeration.values
  implicit def valueToBatchCategory(v: Value): BatchCategory = v match {
    case bc: BatchCategory => bc
    case x => throw new IllegalArgumentException("Value is not a BatchCategory: " + x)
  }

  def valueOf(catStr: String): BatchCategory = {
    BatchCategory.values.
      find { v => val s = v.toString; s.take(1) == catStr || s == catStr }.
      getOrElse(throw new IllegalArgumentException("Unknown category '" + catStr + "' !  "))
  }

  def main(args: Array[String]) {
    BatchCategory.values.foreach(v => println(v + " isOfficial=" + v.isOfficial))
  }
}

prints

OFFICIAL isOfficial=true
TEST isOfficial=false
USER isOfficial=false

This was done for some legacy code that couldn't be moved to a saner enum strategy besides Enumeration.

sourcedelica
  • 23,940
  • 7
  • 66
  • 74
1

After checking out the source code of scala.Enumeration, I got this:


object MyEnum extends Enumeration {
  val ONE = new Val { def method = "1" }
  val TWO = new Val { def method = "2" }
  val THREE = new Val { def method = "3" }
}

It seems hard to get rid of the 'new' since anonymized class is used. If anyone knows how to do it, let me know :)

Shiva Wu
  • 1,074
  • 1
  • 8
  • 20
0

The answer, which tells that Scala enums do not support args/methods-customized values seems wrong. Other answers (some of them involve implicit) demonstrate that it can do but they create impression that demands name duplication: your value has declared name as java object field and, secondly, the name is supplied to the value constructor as string whereas the whole point of Enums is to create a iterable name->value map and scala can do without redundancy:

object Ops1 extends Enumeration {

    protected case class OpsVal(f: Int => Int) extends super.Val(/*nextId*/)

    val ZERO = new FuncVal (x => 0)
    val DOUBLE = new FuncVal (x => 2 * x )

    implicit def convert(v: Value) = v.asInstanceOf[OpsVal]

}

// implicit is not needed
Ops1.ZERO.f(1)                            //> res0: Int = 0

// implicit is needed
Ops1.values map (v => (v + "=>" + v.f(1)))//> res1: scala.collection.immutable.SortedSet[String] = TreeSet(DOUBLE=>2, ZERO=>0)

I think that the above is more concise than

object Ops2 extends Enumeration {

    protected abstract class OpsVal extends Val() {
      def f(a: Int): Int
    }

    val ZERO = new OpsVal { def f(x: Int) = 0 }
    val DOUBLE = new OpsVal { def f(x: Int) = 2 * x }

    implicit def convert(valu: Value) = valu.asInstanceOf[OpsVal]
}
Ops2.ZERO.f(1) // implicit is not needed  //> res2: Int = 0

// implicit is needed
Ops2_3.values map (v => (v, v(1)))        //> res7: scala.collection.immutable.SortedSet[(e.Ops2_3.Value, Int)] = TreeSet
                                              //| ((ZERO,0), (DOUBLE,2))

Since there is a single method per value, we can convert them into the functions

object Ops2_3 extends Enumeration {

    protected case class FuncVal(f: Int => Int) extends Val {
        def apply(x: Int) = f(x) // no need to extend Function1 explicitly
    }

    val ZERO = new FuncVal (x => 0)
    val DOUBLE = new FuncVal (x => 2 * x )

    implicit def convert(v: Value) = v.asInstanceOf[FuncVal]

}
Ops2_3.ZERO(1) // implicit is not needed  //> res6: Int = 0

// implicit is needed
Ops2_3.values map (v => (v, v(1)))        //> res7: scala.collection.immutable.SortedSet[(e.Ops2_3.Value, Int)] = TreeSet
                                              //| ((ZERO,0), (DOUBLE,2))

Functions, shared among all values, can be defined like this (usable in arg parser)

val args: Array[String] = "-silent -samples 100 -silent ".split(" +").toArray
                                              //> args  : Array[String] = Array(-silent, -samples, 100, -silent)
object Opts extends Enumeration {

    val nopar, silent, samples = new Val() {
        def apply() = args.contains(toString)
        def asInt(default: Int) = { val i = args.indexOf(toString) ;  if (i == -1) default else args(i+1).toInt}
        def asInt: Int = asInt(-1)
        override def toString = "-" + super.toString
    }
}

Opts.nopar()                              //> res0: Boolean = false
Opts.samples.asInt                        //> res1: Int = 100

Other users argue for cases of sealed traits + macros, Iteration over a sealed trait in Scala?

Community
  • 1
  • 1
Val
  • 1
  • 8
  • 40
  • 64
  • After doing extensive research on options, I posted a much more complete overview of this domain including a solution to the "sealed trait + case object" pattern where I have solved the JVM class/object initialization ordering problem: http://stackoverflow.com/a/25923651/501113 – chaotic3quilibrium Sep 18 '14 at 23:02
0

Scala3 still has no good solution but it could be done in less/more good way using lambdas or extension function.

1st approach: path method as lambda Disadvantage: impossible to use such methods without parentheses ()

enum Enum1 (val method: ()=>String) derives CanEqual :
  case ONE   extends Enum1(() => "1")
  case TWO   extends Enum1(() => "2")
  case THREE extends Enum1(() => "3")

2nd approach: extension method (you can put it into any 'object', just remember to import it) Advantage: really scala approach Disadvantage: you can miss new enum value in match if you use default compiler settings (there will be only warning) Use compiler param '-Wconf:msg=match may not be exhaustive:error' to get compilation error

enum Enum2 derives CanEqual:
  case ONE, TWO, THREE
object Enum2 :
  extension (en: Enum2)
    // without parentheses () (side-effects are not expected)
    def method1: String = en match
      case Enum2.ONE   => "1"
      case Enum2.TWO   => "2"
      case Enum2.THREE => "3"
    // with parentheses () (side-effects are expected)
    def method2(): String = en match
      case Enum2.ONE   => "11"
      case Enum2.TWO   => "22"
      case Enum2.THREE => "33"

Test


class EnumsTest {
  import scala.language.unsafeNulls
  import org.assertj.core.api.Assertions.assertThat

  @Test
  def enumsTest(): Unit = {
    assertThat(Enum1.ONE.method()).isEqualTo("1")
    assertThat(Enum1.TWO.method()).isEqualTo("2")
    assertThat(Enum1.THREE.method()).isEqualTo("3")

    // of course does not work without using ()
    //assertThat(Enum1.ONE.method).isEqualTo("1")

    assertThat(Enum2.ONE.method1).isEqualTo("1")
    assertThat(Enum2.TWO.method1).isEqualTo("2")
    assertThat(Enum2.THREE.method1).isEqualTo("3")

    assertThat(Enum2.ONE.method2()).isEqualTo("11")
    assertThat(Enum2.TWO.method2()).isEqualTo("22")
    assertThat(Enum2.THREE.method2()).isEqualTo("33")
  }
}