I would like to add a method to scala.Enumeration. My first approach was to try to extend it, but was bitten by this. My second approach was to try to define a method, and pass in the Enumeration - if that worked, I hoped to use an implicit conversion. I'm having a hard time preserving type with the return type of the method, however.
object EnumExample {
object SampleEnum extends Enumeration {
val include, exclude = Value
}
def parse[T <: Enumeration](name:String, enum:T):T#Value =
enum.valueOf(name) match {
case Some(x) => x
case x => throw new RuntimeException("No field named '" + name + "' found on enum " + enum + ", legal values = " + enum.values)
}
def main(args:Array[String]) = {
//compiles fine, and preserves custom type
val withNameExample:SampleEnum.Value = SampleEnum.withName("include")
//also fine, but we lost type info
val enumWithHash:Enumeration#Value = parse("include", SampleEnum)
/**
error: type mismatch;
found : Main.$anon.EnumExample.SampleEnum#Value
required: Main.$anon.EnumExample.SampleEnum.Value
val parseExample:SampleEnum.Value = parse("include", SampleEnum)
*
*/
val customTypeWithHash:SampleEnum.type#Value = parse("include", SampleEnum)
//same error
val customTypeWithDot:SampleEnum.Value = parse("include", SampleEnum)
}
}
One obvious fix would be to just remove the return type declaration from the parse method, but that gives me a "illegal dependent method type". This leaves me with lots of questions:
Is this possible to specify? One way or another, I'd like to get a nice error message when parsing an enumeration field in from a String.
Why do I get the "illegal dependent method type"?
What precisely is the "#" operator(?) in this case?
This looks like a bug to me (at least in 2.8.0 Beta1 where I've tested it).
Particularly instructive is the following:
scala> var x: SampleEnum.type#Value = null
x: SampleEnum.Value = null
Here, we're requesting the arbitrary inner type but we're actually getting the specific inner type. That's just broken (and I will file a bug report if there is not already one, unless someone else swiftly explains why this is not a bug).
So, what to do? Well, first, let's understand the original method signature of parse:
def parse[T <: Enumeration](name:String, enum:T):T#Value
We have T
which is a subclass of Enumeration
, enum
which is an instance of T
, and--since there is no way to express that the Value
must be from that particular instance of T
, we have to resort to T#Value
(i.e. an inner type of T
, without regard to which particular T
it comes from).
Now we have to pass in a specific object, get back a generic inner object, and do it in the face of ExampleObject.type#Value
being the same as ExampleObject.Value
even though they type differently.
So we have to write our own object from scratch:
class SampleWorkaroundClass extends Enumeration {
val include, exclude = Value
}
lazy val SampleWorkaround = new SampleWorkaroundClass
Here we have a single instantiation of a specially defined class. Now we can get around the bug in Object
:
scala> val typeWorks:SampleWorkaroundClass#Value = parse("include",SampleWorkaround)
typeWorks: SampleWorkaroundClass#Value = include
(The lazy val
is just to get exactly the same behavior as with objects where they're not instantiated until they're used; but a val
alone would be fine.)
Edit: Outer#Inner
means "any inner class of type Inner
coming from this outer class" as opposed to myOuter.Inner
which means "only that class of type Inner
that has this instance of Outer
, myOuter
, as its enclosing class". Also, I don't get the dependent type error in 2.8.0 Beta1--but not being able to specify the type makes things quite awkward.
Edit: update on bug report--the way it works now is apparently intentional. To use types in this fashion, you are intended to explicitly specify the type in the function call (as it is not inferred to be what you want), like so
val suggested: SampleEnum.type#Value = parse[SampleEnum.type]("include",SampleEnum)
This way is easier if you only have to do this a few times. If you have to do it many times, creating your own class with a val (or lazy val) instantiation probably makes things easier / more compact.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With