Reputation: 994
I have two questions about nested types in Scala.
Imagine I have this kinda trait;
trait ScanList[E] {
sealed trait Command
case object Recover extends Command
case class Remove(item: E) extends Command
sealed trait Event
case class Removed(item: E) extends Event
}
And now I want to write a generic trait over this like this (the problems are encoded in the pattern matches as comment):
trait ScanListProcessor[E] {
type SL = ScanList[E]
def process(msg: SL#Command) = {
msg match {
case u:SL#Remove => // how can instantiate SL#Removed here?
case SL#Recover => //cannot match on nested objects?
}
}
}
The reason for using a trait is that I can derive new implementations of ScanList. In this trait I also have operations like def shouldProcess(item: E): Boolean
. For each implementation of ScanList[E]
I would like to write generic behaviour like depicted above.
SL#Removed
? I guess it's the same having a generic parameter and trying to construct a value from that, type classes would solve this?Upvotes: 1
Views: 1531
Reputation: 13667
Nested traits, class, and objects in Scala act like inner classes in Java. Any instance of them that you create is associated with an instance of the parent class/trait that they are created from. So continuing from your example:
val sl1 = new ScanList[Int] {}
val sl2 = new ScanList[Int] {}
val r1 = sl1.Removed(1) // has type sl1.Removed
val r2 = sl2.Removed(2) // has type sl2.Removed
val rs = List(r1, r2) // has type List[ScanList[Int]#Removed]
You can pattern match on them like:
rs match {
case List(sl1.Removed(x), sl2.Removed(y)) => (x, y)
}
Pattern matching requires that you explicitly references the parent instance that the nested instance belongs to. Here, we separately match on sl1.Removed
and sl2.Removed
.
As for you second question, you cannot create a SL#Removed
without having a parent instance available. With one, it is as simple as sl1.Removed(1)
.
Your ScanListProcessor
could be rewritten to take the ScanList
it operates on as a value:
class ScanListProcessor[E](val sl: ScanList[E]) {
def process(msg: sl.Command) = {
msg match {
case sl.Remove(x) => sl.Removed(x)
case sl.Recover => ???
}
}
}
But this is awkward, and nested types probably aren't needed here. If all you want to do is group some traits, classes etc. together in a namespace, then put them in a package
or object
instead of a trait
or class
. In this case you would need to move the type parameter E
down onto Remove
and Removed
themselves:
object ScanList {
sealed trait Command
case object Recover extends Command
case class Remove[E](item: E) extends Command
sealed trait Event
case class Removed[E](item: E) extends Event
}
Upvotes: 5