UPDATE - 2014/Sep/17
It turns out that even the solution in the prior update (from 2013/Feb/19) fails to work if one places println(Value.Player2)
as the first command; i.e. the ordinals are still assigned incorrectly.
I have since created a verifiable working solution as a Gist. The implementation waits to assign the ordinals until after all JVM class/object initialization completes. It also facilitates extending/decorating each enumeration member with additional data while still being very efficient for (de)serialization.
I have also created a StackOverflow answer which elaborates on all the different enumeration patterns being used in Scala (including the solution in the Gist I mention above).
I am working with a fresh install of the TypeSafe IDE (Eclipse with ScalaIDE pre-installed). I'm on Windows 7-64bit. And I have had mixed success with the Scala Worksheet. It has already hard crashed my machine (to a full reset or once to the blue screen of death) three times in less than an hour. So, this may be a bug in the Scala Worksheet. I'm not sure yet and don't have time to chase down that issue. However, this enum issue is stopping me from testing.
I am using the following code in the Scala Worksheet:
package test
import com.stack_overflow.Enum
object WsTempA {
object Value extends Enum {
sealed abstract class Val extends EnumVal
case object Empty extends Val; Empty()
case object Player1 extends Val; Player1()
case object Player2 extends Val; Player2()
}
println(Value.values)
println(Value.Empty)
}
The above works fine. However, if you comment out the first println, the second line throws an exception: java.lang.ExceptionInInitializerError. And I am just enough of a Scala newbie to not understand why it's occurring. Any help would be deeply appreciated.
Here's the stack trace from the right side of the Scala Worksheet (left side stripped to display nicely here):
java.lang.ExceptionInInitializerError
at test.WsTempA$Value$Val.<init>(test.WsTempA.scala:7)
at test.WsTempA$Value$Empty$.<init>(test.WsTempA.scala:8)
at test.WsTempA$Value$Empty$.<clinit>(test.WsTempA.scala)
at test.WsTempA$$anonfun$main$1.apply$mcV$sp(test.WsTempA.scala:14)
at org.scalaide.worksheet.runtime.library.WorksheetSupport$$anonfun$$exe
cute$1.apply$mcV$sp(WorksheetSupport.scala:76)
at org.scalaide.worksheet.runtime.library.WorksheetSupport$.redirected(W
orksheetSupport.scala:65)
at org.scalaide.worksheet.runtime.library.WorksheetSupport$.$execute(Wor
ksheetSupport.scala:75)
at test.WsTempA$.main(test.WsTempA.scala:11)
at test.WsTempA.main(test.WsTempA.scala)
Caused by: java.lang.NullPointerException
at test.WsTempA$Value$.<init>(test.WsTempA.scala:8)
at test.WsTempA$Value$.<clinit>(test.WsTempA.scala)
... 9 more
The class com.stack_overflow.Enum comes from this StackOverflow thread. I have pasted in my version here for simplicity (in case I missed something critical during the copy/paste operation):
package com.stack_overflow
//Copied from https://mcmap.net/q/101162/-case-objects-vs-enumerations-in-scala
abstract class Enum {
type Val <: EnumVal
protected var nextId: Int = 0
private var values_ = List[Val]()
private var valuesById_ = Map[Int ,Val]()
private var valuesByName_ = Map[String,Val]()
def values = values_
def valuesById = valuesById_
def valuesByName = valuesByName_
def apply( id : Int ) = valuesById .get(id ) // Some|None
def apply( name: String ) = valuesByName.get(name) // Some|None
// Base class for enum values; it registers the value with the Enum.
protected abstract class EnumVal extends Ordered[Val] {
val theVal = this.asInstanceOf[Val] // only extend EnumVal to Val
val id = nextId
def bumpId { nextId += 1 }
def compare( that:Val ) = this.id - that.id
def apply() {
if ( valuesById_.get(id) != None )
throw new Exception( "cannot init " + this + " enum value twice" )
bumpId
values_ ++= List(theVal)
valuesById_ += ( id -> theVal )
valuesByName_ += ( toString -> theVal )
}
}
}
Any sort of guidance would be greatly appreciated.
UPDATE - 2013/Feb/19
After several cycles with Rex Kerr, here is the updated versions of the code that now works:
package test
import com.stack_overflow.Enum
object WsTempA {
object Value extends Enum {
sealed abstract class Val extends EnumVal
case object Empty extends Val {Empty.init} // <---changed from ...Val; Empty()
case object Player1 extends Val {Player1.init} // <---changed from ...Val; Player1()
case object Player2 extends Val {Player2.init} // <---changed from ...Val; Player2()
private val init: List[Value.Val] = List(Empty, Player1, Player2) // <---added
}
println(Value.values)
println(Value.Empty)
println(Value.values)
println(Value.Player1)
println(Value.values)
println(Value.Player2)
println(Value.values)
package com.stack_overflow
//Copied from https://mcmap.net/q/101162/-case-objects-vs-enumerations-in-scala
abstract class Enum {
type Val <: EnumVal
protected var nextId: Int = 0
private var values_ = List[Val]()
private var valuesById_ = Map[Int ,Val]()
private var valuesByName_ = Map[String,Val]()
def values = values_
def valuesById = valuesById_
def valuesByName = valuesByName_
def apply( id : Int ) = valuesById .get(id ) // Some|None
def apply( name: String ) = valuesByName.get(name) // Some|None
// Base class for enum values; it registers the value with the Enum.
protected abstract class EnumVal extends Ordered[Val] {
val theVal = this.asInstanceOf[Val] // only extend EnumVal to Val
val id = nextId
def bumpId { nextId += 1 }
def compare(that: Val ) = this.id - that.id
def init() { // <--------------------------changed name from apply
if ( valuesById_.get(id) != None )
throw new Exception( "cannot init " + this + " enum value twice" )
bumpId
values_ ++= List(theVal)
valuesById_ += ( id -> theVal )
valuesByName_ += ( toString -> theVal )
}
}
}
Empty.init
--justinit
is fully adequate. – Elsa