Reputation: 7032
I'm learning Scala coming from a Java background, and the first thing I've found that works significantly differently than Java are the Enums. I've managed to accomplish everything I've wanted to just by trial and error, but I'd love to better understand what I'm doing along the way.
From the Scala documentation, I'm told to create an enum by extending the class Enumeration
, and add values by setting them equal to a constant Value
, eg:
object Label extends Enumeration{
val NONE = Value
}
This works about as expected. My trouble comes in using not only enums but extensions of custom written enum extensions. I wrote a chunk of code as part of a Machine Learning class (now over) to separate data by their labels (for use in TDIDT, for example). At the bottom is a small piece of it to get at where I'm confused. The Data
object is runnable, just to try it out.
First, on the print statement, I thought it would be true, but it is not
println(Label.NONE.equals(MessageLabel.NONE))//Thought this would be true, is false
Why is this the case? Is that even though the NONE
that MessageLabel has inherited is directly from Label, the type system insists that they are different enum values?
Secondly and more importantly I've been going back and forth between Label.Value
and Label#Value
basically willy-nilly. The version that I posted with:
def splitByLabel[T <: Label#Value]
trait Labelable[T <: Label#Value]
abstract class Data[T <: Label#Value]
class Message( ... val label : MessageLabel.Value)
Compiles and runs correctly. When I change all the #
s to .
, I get a compile time error on the line splitByLabel(messages).foreach(a => println(a))
, stating:
Inferred type arguments [MessageLabel.Value] do not conform to method splitByLabel's type parameter bounds[T <: Label.Value]
But when I change all the .
s to #
s, I get a compile time error on the line class Message(val index : Int, val s : Map[Double, Int], override val label : MessageLabel#Value) extends Data[MessageLabel#Value](label)
, stating:
Not Found: Type MessageLabel
So clearly there is a difference between the two and they each fill a specific role. Can someone help me understand what the difference is? Thank you!
/** Enum type all labels should extend. Guarantees access of universal NONE label */
class Label extends Enumeration{
val NONE = Value
}
/** Singleton instance for accessing NONE */
object Label extends Label{}
/** Companion object to all data classes. Hosts helper methods and a runnable main method */
object Data{
/** Returns a map of lists, each list is similarly labeled data. Map is label -> list of data */
def splitByLabel[T <: Label#Value](elms : List[Labelable[T]]) : Map[T, List[Labelable[T]]] = {
def f(acc : Map[T, List[Labelable[T]]], e : Labelable[T]) : Map[T, List[Labelable[T]]] = {
if(acc.contains(e.label)){
val l = acc(e.label)
acc - e.label + ((e.label, (e :: l)))
} else{
acc + ((e.label, List(e)))
}
}
elms.foldLeft(Map[T, List[Labelable[T]]]())(f)
}
def main(args : Array[String]){
println(Label.NONE.equals(MessageLabel.NONE))
val messages : List[Message] = (0 to 10).toList.map(a =>
new Message(a, Map(), if(a % 3 == 0) MessageLabel.HAM else MessageLabel.SPAM))
splitByLabel(messages).foreach(a => println(a))
}
}
/** Implementing classes can be labeled */
trait Labelable[T <: Label#Value]{
/** Returns the label of this thing */
val label : T
/** The possible labelings for this thing */
val labels : List[T]
}
abstract class Data[T <: Label#Value](override val label : T) extends Labelable[T]{
override def toString(): String = {
if (label != null)
label.toString
else
"NO_LABEL"
}
}
object MessageLabel extends Label{
val HAM, SPAM = Value
}
/** An instance represents a sentence. */
class Message(val index : Int, val s : Map[Int, Double], override val label : MessageLabel.Value)
extends Data[MessageLabel.Value](label){
/** Returns the possible labelings for a message */
override val labels = MessageLabel.values.toList
/** Adds index to tostring at front */
override def toString() : String = {
index + "-" + super.toString
}
}
Upvotes: 0
Views: 615
Reputation: 39577
This is not particular to Enumeration.
scala> class A { class B ; val None = new B }
defined class A
scala> class C extends A ; class D extends A
defined class C
defined class D
scala> val c = new C ; val d = new D
c: C = C@45fe3ee3
d: D = D@4cdf35a9
scala> c.None == d.None
res0: Boolean = false
No one would ever expect that to be true. One value is initialized in one (super-) constructor, another in the other.
Also, Value
is not a constant; it's a function that says, "Give me another Value." So you're generating a value for each instance.
In Java, you can't extend enums in this sense. To "extend" is to add members or increase the extension, but subclassing means a subset or restricted domain.
This is a case where one prefers composition over inheritance. Given a set of weekdays and of weekend days, I get alldays by adding them, not by extending weekdays with the weekend.
Here is an example of using an Enumeration in a path-dependent way.
Another issue with the code as it stands:
scala> MessageLabel.NONE
res4: MessageLabel.Value = <Invalid enum: no field for #0>
https://issues.scala-lang.org/browse/SI-5147
Upvotes: 2
Reputation: 17431
Label#Value
is the type Value
in the type Label
. Label.Value
is the type Value
in the value Label
. (It's a bit confusing because you have both class Label
and object Label
(i.e. a value)). So a MessageLabel.Value
is a Label#Value
, because MessageLabel
is an instance of the type (class
) Label
. But it isn't a Label.Value
, because MessageLabel
isn't the value (object
) Label
. And there is no MessageLabel#Value
because there is no class MessageLabel
(or trait).
(FWIW I find scala Enumeration
very confusing and prefer to use Java enums in my Scala code)
Upvotes: 2