-
-
Save viktorklang/1057513 to your computer and use it in GitHub Desktop.
trait Enum { //DIY enum type | |
import java.util.concurrent.atomic.AtomicReference //Concurrency paranoia | |
type EnumVal <: Value //This is a type that needs to be found in the implementing class | |
private val _values = new AtomicReference(Vector[EnumVal]()) //Stores our enum values | |
//Adds an EnumVal to our storage, uses CCAS to make sure it's thread safe, returns the ordinal | |
private final def addEnumVal(newVal: EnumVal): Int = { import _values.{get, compareAndSet => CAS} | |
val oldVec = get | |
val newVec = oldVec :+ newVal | |
if((get eq oldVec) && CAS(oldVec, newVec)) newVec.indexWhere(_ eq newVal) else addEnumVal(newVal) | |
} | |
def values: Vector[EnumVal] = _values.get //Here you can get all the enums that exist for this type | |
//This is the trait that we need to extend our EnumVal type with, it does the book-keeping for us | |
protected trait Value { self: EnumVal => //Enforce that no one mixes in Value in a non-EnumVal type | |
final val ordinal = addEnumVal(this) //Adds the EnumVal and returns the ordinal | |
def name: String //All enum values should have a name | |
override def toString = name //And that name is used for the toString operation | |
override def equals(other: Any) = this eq other.asInstanceOf[AnyRef] | |
override def hashCode = 31 * (this.getClass.## + name.## + ordinal) | |
} | |
} | |
//And here's how to use it, if you want compiler exhaustiveness checking | |
object Foos extends Enum { | |
sealed trait EnumVal extends Value /*{ you can define your own methods etc here }*/ | |
val F = new EnumVal { val name = "F" } | |
val X = new EnumVal { val name = "X" } | |
} | |
/** | |
scala> Foos.values.find(_.name == "F") | |
res3: Option[Foos.EnumVal] = Some(F) | |
scala> Foos.X.ordinal | |
res4: Int = 1 | |
scala> def doSmth(foo: Foos.EnumVal) = foo match { | |
case Foos.X => println("pigdog") | |
} | |
<console>:10: warning: match is not exhaustive! | |
missing combination $anon$1 | |
missing combination $anon$2 | |
scala> def doSmth(foo: Foos.EnumVal) = foo match { | |
case Foos.X => println("pigdog") | |
case Foos.F => println("dogpig") | |
} | |
doSmth: (foo: Foos.EnumVal)Unit | |
**/ | |
//But if you don't care about getting exhaustiveness warnings, you can do: | |
object Foos extends Enum { | |
case class EnumVal private[Foos](name: String) extends Value /* { you can define your own methods and stuff here } */ | |
val F = EnumVal("F") | |
val X = EnumVal("X") | |
} | |
/** | |
Which is a bit less boilerplatey. | |
Cheers, | |
√ | |
**/ |
I have copied this verbatim in Scala IDE 3.0.4 with Scala 2.11.2. And I am getting an exhaustive check warning for the code snippet Victor Klang shows as part of his REPL session; i.e. this one:
def doSmth(foo: Foos.EnumVal) = foo match {
case Foos.F => println("dogpig")
case Foos.X => println("pigdog")
}
Has something changed in Scala between the time Victor wrote this (mid 2011) and today (mid 2014)?
Summary:
I can’t get Viktor’s code to do pattern matcher exhaustiveness checking. I figured out how to do it using case objects while also eliminating the class/object initialization ordering issues (which cause the ordinals to not consistently be assigned to the same case object instances). Here’s the tinyurl (longer one below): http://goo.gl/zlCs61
Details:
I have so longed for Java’s Enum in Scala (without having to created Scala/Java mixed project and resorting to using Java’s Enum). As I began a large project several months ago, I decided to try and get the Scala enumeration problem solved again. I was very hopeful someone had created a DIY solution...which worked. When I saw Viktor’s come up in my Google search, I was pretty excited, but...
I have tried extensively to get Viktor's code above to work. I have not found a way to get it to provide pattern matching exhaustiveness checking. And I have tried LOTS of variations.
So, I decided to to move to using case objects (as opposed to a series of val-s) which I knew worked with the pattern matcher’s exhaustiveness checker. I got right to the edge of something that worked exactly like I wanted...when I exposed an issue with class/object initialization ordering, which Rex Kerr and I worked through on StackOverflow (a couple of years ago):
http://stackoverflow.com/questions/14947179/using-a-custom-enum-in-the-scala-worksheet-i-am-receiving-an-error-java-lang-ex
ARGH!!!!!!!!
So, I have now invested a considerable amount of time wrestling many Scala and JVM demons to hack my way to runtime victory. Here is a link to org.public_domain.scala.utils.Enumeration which provides the closest analog to all of the benefits I got from Java’s Enum:
Final solution:
https://gist.github.com/chaotic3quilibrium/57add1cd762eb90f6b24#file-org-public_domain-scala-utils-enumeration-scala
Example usages:
Simple: https://gist.github.com/chaotic3quilibrium/57add1cd762eb90f6b24#file-org-public_domain-chess-chesspiecessimplest-scala
I would appreciate any feedback you might have on this.
Thank you.
@chaotic3quilibrium - Wow. That code is pretty intense! Any thoughts on releasing it as a library?
@sweepy84 - we can NOT extends an object