Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using sealed trait as a key for a map

Tags:

scala

traits

I am trying to define a map from instances of a sealed trait. In the following code, Scala seems to infer the key type as Product with Serializable with Day:

object Test extends App {
  sealed trait Day
  case object Sunday extends Day
  case object Monday extends Day
  case object Tuesday extends Day

  val m: Map[Day, Int] = Map(Sunday -> 17, Monday -> 4).withDefaultValue(0)
}

This does not compile:

Test.scala:7: error: type mismatch;
 found   : scala.collection.immutable.Map[Product with Serializable with Test.Day,Int]
 required: Map[Test.Day,Int]
Note: Product with Serializable with Test.Day <: Test.Day, but trait Map is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: Test.Day`. (SLS 3.2.10)
  val m: Map[Day, Int] = Map(Sunday -> 17, Monday -> 4).withDefaultValue(0)

I can change the key type in the definition of m, but that would mean repeating Product with Serializable with Day in many places. Another option I found was to change the definition of the trait to:

sealed trait Day extends Product with Serializable

As there are many advantages for using sealed traits and case objects instead of enums, I am wondering what would be a good approach to put them as keys in a map.

like image 942
thesamet Avatar asked Feb 27 '15 19:02

thesamet


2 Answers

Because Map needs the keys to have properties which are defined in Product and Serializable, so Scala implicitly creates anonymous class which extends your class with Product and Serializable which provides default implementations of equals and hash.

object Test extends App {
  trait PS extends Product with Serializable
  sealed trait Day extends PS
  case object Sunday extends Day
  case object Monday extends Day
  case object Tuesday extends Day

  val m: Map[Day, Int] = Map(Sunday -> 17, Monday -> 4).withDefaultValue(0)
}
like image 120
sarveshseri Avatar answered Sep 25 '22 06:09

sarveshseri


The fundamental reason you are seeing this is that

  1. the compiler infers by default the most specific type, which is in this case Product with Serializable with Test.Day. The Product and Serializable classes are implicitly implemented by all case classes and case objects in Scala, so all case objects have them in common. Hence, this is truly the most specific common type of Sunday and Monday,

  2. traits on the other hand, do not implement Product nor Serializable.

Thus, when you require somewhere Day, the more specific, inferred type will not do (also because, as the error message indicates, the K type parameter of Map is invariant.)

One way, like indicated by the answer by Sarvesh Kumar Singh, is to have your trait extend Product and Serializable, but in my view the better one (pointed out by Rüdiger Klaehn) is to tell the compiler that you are actually fine with the more general type Map[Day, Int] by making the type explicit:

val m = Map[Day, Int](Sunday -> 17, Monday -> 4).withDefaultValue(0)
like image 44
LCC Avatar answered Sep 25 '22 06:09

LCC