Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add a method to Enumeration in Scala?

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:

  • https://github.com/rbricks/itemized
  • http://pedrorijo.com/blog/scala-enums/
like image 644
Etam Avatar asked Dec 03 '10 14:12

Etam


People also ask

Can enumerations have methods?

The enum class body can include methods and other fields. The compiler automatically adds some special methods when it creates an enum. For example, they have a static values method that returns an array containing all of the values of the enum in the order they are declared.

How do I create an Enumeration in Scala?

In Scala, there is no enum keyword unlike Java or C. Scala provides an Enumeration class which we can extend in order to create our enumerations. Every Enumeration constant represents an object of type Enumeration. Enumeration values are defined as val members of the evaluation.

Can you assign values to enums?

You can assign different values to enum member. A change in the default value of an enum member will automatically assign incremental values to the other members sequentially.

Can different Enumeration may have same name?

1. Two enum names can have same value. For example, in the following C program both 'Failed' and 'Freezed' have same value 0.


10 Answers

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) 
like image 144
Sean Ross Avatar answered Oct 05 '22 23:10

Sean Ross


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.

like image 32
Aaron Novstrup Avatar answered Oct 05 '22 21:10

Aaron Novstrup


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:

  • Is it possible to “explore” which objects are defined within an other object via reflection at runtime?
  • How to access objects within an object by mixing in a trait with reflection?

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.

like image 31
soc Avatar answered Oct 05 '22 23:10

soc


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.

like image 35
missingfaktor Avatar answered Oct 05 '22 22:10

missingfaktor


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

like image 43
ozeebee Avatar answered Oct 05 '22 22:10

ozeebee


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)
like image 27
oxbow_lakes Avatar answered Oct 05 '22 21:10

oxbow_lakes


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
     */
  }

} 
like image 21
Jan van der Vorst Avatar answered Oct 05 '22 23:10

Jan van der Vorst


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")
    }
  }
}
like image 33
Etam Avatar answered Oct 05 '22 23:10

Etam


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 :)

like image 29
Shiva Wu Avatar answered Oct 05 '22 21:10

Shiva Wu


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.

like image 43
sourcedelica Avatar answered Oct 05 '22 22:10

sourcedelica