mark_dj
mark_dj

Reputation: 994

Nested types in Scala

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.

  1. How can pattern match on nested object in a generic type?
  2. Is it possible to instantiate from a type constructor? For example: 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

Answers (1)

wingedsubmariner
wingedsubmariner

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

Related Questions