This page is no longer maintained — Please continue to the home page at www.scala-lang.org

Alternate approach to Enum

No replies
JamesJ
Joined: 2010-01-24,
User offline. Last seen 42 years 45 weeks ago.
I haven't been quite satisfied with enums in Scala.  The official approaches seem to be either to extend Enumeration or use sealed case classes.  The topic seems to have some coverage on StackOverflow.  Extending Enumeration makes it tough to add custom stuff to your objects beyond a simple string and the compiler doesn't know about all of the cases like case classes.  However, case classes don't give you the handy ability to iterate through the values.  
I also saw that someone else posted a hybrid approach.
After playing with a few ideas, I have a rough solution that scratches my itch a bit better, but introduces it's own dangers.  I don't believe that this is "the" good solution.  We may need the Scala language to grow a bit more before we get a good robust approach that has all of the good stuff.
I basically just added iteration to the case class approach.  This way it is easy to define the base class used by the values, and the compiler can see the objects as a complete set.  Because I couldn't do it "right" I added some stuff to help me from doing some obvious dumb stuff.
I used lazy val initialization to help with defining the iteration order of the objects.  Objects don't get created until they get referenced, so by defining the order, all of the objects in the order get initialized, and then the defineOrder function "defines" the enum.  If another object is later referenced that was not included in the official order, it will complain.  Likewise, if you try to do anything interesting without defining order, it will also complain.  
There are parts of the approach that are a bit sloppy.  I.e. the "values" list is a list of the items as the base "value" class, while the user has to define their own more specifically typed list "order" if needed.  I tried to work around this, but the Java erasure and Scala manifests etc. just left me a bit bloodied before I gave up and stuck with the simple approach that worked well enough.  
This is my first pass prototype.  Seems to do most of what I wanted, but I was interested in feedback.  
abstract class AltEnum{    private var orderDefined = false     private var privValues:Option[List[Value]] = None    lazy val values:List[Value] = {        if( ! orderDefined ) throw new Error("Tried to access id without defining order")         // this shouldn't be empty if orderDefined        if( privValues.isEmpty ) throw new Error("Unexpected empty priv value")        privValues.get    }    def defineOrder[T <: Value](list:List[T]):List[T]={         orderDefined = true        var nextId = 0        list.foreach( (v)=>{            v.privId = nextId            nextId += 1            v.id // cause id to init         })        privValues = Some(list)        list    }
    // look through list and find value with id    def getById[T<:Value](id:Int, list:List[T]):Option[T]={         list.find((i)=>{i.id == id})    }    // look through list and find value with id    def getByName[T<:Value](name:String, list:List[T]):Option[T]={         list.find((i)=>{i.name == name})    }
    protected class Value(){        if( orderDefined ) throw new Error("Created value or accessed value that was not in defined order")         private[AltEnum] var privId:Int = -1        lazy val id = {            if( ! orderDefined ) throw new Error("Tried to access id without defining order")            privId         }        lazy val name = {            if( ! orderDefined ) throw new Error("Tried to access name without defining order")            this.toString        }     }}

Here is a sample usage:
object JunkEnum extends AltEnum{    sealed class Junk(val someString:String) extends Value
    case object FIRST extends Junk("Wow")    case object SECOND extends Junk("No Way")    case object THIRD extends Junk("Yah")
    val order = defineOrder(FIRST::SECOND::THIRD::Nil)
}
object Main2 {    def main(args: Array[String]): Unit = {        println("enum list :" + JunkEnum.order)         import JunkEnum._        for( j <- order){            println(" "+j.name+"  id = "+j.id+"  someString = "+j.someString)         }    }}

Output:
enum list :List(FIRST, SECOND, THIRD) FIRST  id = 0  someString = Wow SECOND  id = 1  someString = No Way  THIRD  id = 2  someString = Yah

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland