-
-
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, | |
√ | |
**/ |
Thanks Viktor. Having seen it I feel dumb ;-)
One thing that the Java enums suffer from is their lack of extensibility. I'm gonna take it and make something like this http://blogs.oracle.com/darcy/entry/enums_and_mixins
Good luck.
Sounds like a plan... go for it!
Oh, by the way... and now I really am showing my lack of Scala education / grokness...
I want to do something like this:
trait SomethingWithSize {
def size: Int
}
trait EnumWithSize extends Enum with SomethingWithSize
and then use it like this:
def convertStringToMap(inputString: String, mappings: EnumWithSize) {
val mappingsExpectedLength = mappings.values.map(_.size).sum
// more stuff...
}
object HeaderMap extends EnumWithSize {
case class EnumVal private[HeaderMap](name: String, size: Int) extends Value with EnumWithSize
val HEADER_EL = EnumVal("HEADER_EL", 1)
val HEADER_RESERVED1 = EnumVal("HEADER_RESERVED1", 5)
val HEADER_RESERVED2 = EnumVal("HEADER_RESERVED2", 2)
val HEADER_MESSAGE_TYPE = EnumVal("HEADER_MESSAGE_TYPE", 4)
....
}
But oops! And now I'm getting my knickers in a twist...
:10: error: object creation impossible, since method size in trait SomethingWithSize of type => Int is not defined
I hope you can see my intention from this jibberish.... I want to extend Enum so that I can have something with a size...
Any tips welcome!
Cheers
ray
I was close ;-) Thanks Viktor, I owe you a beer which I will happily provide if you are at Devoxx again this year...
I made one further change, as the code as it stands does not allow the names to be accessed in a generic fashion ;-)
trait SomethingWithANameAndASize {
def size: Int
def name: String
}
trait EnumWithSize extends Enum {
type EnumVal <: Value with SomethingWithANameAndASize
}
object TqsHeaderMap extends EnumWithSize {
case class EnumVal private[TqsHeaderMap](name: String, size: Int) extends Value with SomethingWithANameAndASize
val HEADER_EL = EnumVal("HEADER_EL", 1)
val HEADER_RESERVED1 = EnumVal("HEADER_RESERVED1", 5)
val HEADER_RESERVED2 = EnumVal("HEADER_RESERVED2", 2)
val HEADER_MESSAGE_TYPE = EnumVal("HEADER_MESSAGE_TYPE", 4)
}
def convertStringToMap(inputString: String, mappings: EnumWithSize): Map[SomethingWithANameAndASize, String] = {
val mappingsExpectedLength = mappings.values.map(_.size).sum
if (inputString.length != mappingsExpectedLength)
throw new IllegalArgumentException("Actual length " + inputString.length + " not equal to expected record size of " + mappingsExpectedLength)
var end = 0
(mappings.values.map {
field =>
val start = end
end += field.size
(field, inputString.substring(start, end))
}).toMap
}
class TqsMappingTests extends Spec with ShouldMatchers {
describe("record header mapping") {
val input = "L 0030"
describe("with a string \"" + input + "\" mapped to 4 fields") {
val result = new RecordToMap().convertStringToMap(input, TqsHeaderMap)
it("should be 4 items in length") {
result.size should be(4)
}
it("should have a head of L") {
result.values.head should be("L")
}
it("should be able to access a random value using the Enum symbolic name") {
result.get(TqsHeaderMap.HEADER_MESSAGE_TYPE) should equal(Some("0030"))
}
}
}
}
And I am done! Thanks for the great support Viktor.
What?
scala> F.x.name
res0: String = foo
scala> def foo(e: EnumWithSize) = e.values.map(_.name)
foo: (e: EnumWithSize)scala.collection.immutable.Vector[String]
I should explain further.... If my function signature is:
def convertStringToMap(inputString: String, mappings: EnumWithSize): Map[EnumWithSize, String]
Then the caller cannot access _.name in the values returned in the map without including the EnumVal type of TqsHeader.... and that's not good.
And talking of surprises... I have done some tests on the real code that I need to write and observe that the order of the values is not always aligned with the order of the declaration of the enumeration. For example if I parse a record of 500 characters into 50 parts the head is not always the first element declared. It might not be a bug but it surprised me. I remember you tweeted a while ago about feeling bad about opening tickets on source projects. Imagine how bad I feel, bitching about a GIST ;-)
Ha! It is me being daft... I am actually using the values from the map that I return in my function. Panic over ;-)
Unfortunately, anonymous classes extending a sealed trait don't seem to work with exhaustiveness checks:
val F = new EnumVal { val name = "F" }
val X = new EnumVal { val name = "X" }
then
scala> def doSmth(foo: Foos.EnumVal) = foo match {
case Foos.X => println("pigdog")
case Foos.F => println("dogpig")
}
<console>:10: warning: match is not exhaustive!
missing combination $anon$1
missing combination $anon$2
Actually, even in your example where one enum value is missing, the warning message you listed shows both as a missing combination.
I tried this with 2.9.1, 2.9.0-1 and the 2.8 which runs on simplyscala.com
Was just trying to confirm that it's a compiler bug and not by design. In order to do this, I wanted to find the version where it was supposed to work, but so far without much success.
I did manage to get the REPL on simplyscala.com to show no warnings whatsoever for both the missing combination and for all enum values, so I thought it could be a class previously loaded which interfered. Maybe the same happened when you tried this? There's some evidence that something was already wrong if you've pasted the console session verbatim.
So I'm wondering- did it really work when you tried it or was it just a glitch of the REPL? I've already calculated that at the time you posted this, the stable version was 2.9.0-1, and it doesn't work there.
Even if it never worked, we should file it as a feature request because I want my statically checked enumeration matching!
A simple addition which I find useful:
def apply(i:Int) = vectors(i)
This allows code like Foos(0)
to lookup an enum instead of Foos.values(0)
A noob question. I would like to extend an Enum
e.g.
object Foos extends Enum {
case class EnumVal private[Foos](name: Int) extends Value
val A = EnumVal(1)
val B = EnumVal(2)
}
object AnotherFoos extends Foos{
val C = EnumVa(3)
}
so I could call AnotherFoos(1).
@sweepy84 - we can NOT extends an object
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?
Hey Ray,
here's an example:
scala> object Foos extends Enum {
| case class EnumVal private[Foos](name: String) extends Value {
| def bippy(i: Int) = name + i
| }
|
| val F = EnumVal("F")
| val X = EnumVal("X")
| }
defined module Foos
scala>
scala> Foos.F.bippy(3)
res1: java.lang.String = F3
scala>