Created
June 30, 2011 23:12
-
-
Save viktorklang/1057513 to your computer and use it in GitHub Desktop.
DIY Scala Enums (with optional exhaustiveness checking)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
√ | |
**/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
Enhanced:
https://gist.github.com/chaotic3quilibrium/57add1cd762eb90f6b24#file-org-public_domain-chess-chesspiecesenhanced-scala
I would appreciate any feedback you might have on this.
Thank you.