Strange Error With Scala Generics Implementation

Viewed 52

I have the following trait:

trait SomeTrait {
  def doSomething[V](msg: Message[V]): Message[V]
}

The Message[V] is a case class as below:

case class Message[V](elems: Map[String, V])

I now have a method that calls the doSomething method as below:

  val someWork = new SomeTrait {
    override def doSomething[Int](msg: Message[Int]): Message[Int] = {
      msg.copy(elems = msg.elems.map {
        case (k, v) => (k, v + 1) // It fails here!!!
      })
    }
  }

I get a strange compiler error saying that:

Error:(16, 32) type mismatch;
 found   : Int(1)
 required: String
Error occurred in an application involving default arguments.
        case (k, v) => (k, v + 1)

Was I stupid somewhere?

2 Answers

Beware: your Int is not scala Int. You are hiding the Scala Int behind a generic type name - what you write looks like a template specialization in C++, but generics do not work this way in Scala. You will get the same error when you replace [Int] with [XXX] in your code.

You cannot change the type signature when override the method. As the trait promises you accept any type V without any constraints, the implementation needs to satisfy this promise.

C++-like specialization can be achieved using type classes - you extend your contract by promising you will provide the specialized implementations as needed:

case class Message[V](elems: Map[String, V])

trait SomeTrait {
  def doSomething[V: MapValue](msg: Message[V])
}

trait MapValue[V] {
  def map(v: V): V
}

implicit object MapValueInt extends MapValue[Int] {
  def map(v: Int) = v + 1
}

val someWork = new SomeTrait {
  override def doSomething[X: MapValue](msg: Message[X]) = {
    val evidence = implicitly[MapValue[X]]
    msg.elems.map {
      case (k, v) => k -> evidence.map(v)
    }
  }
}

What @Suma said ... You could also parametrize the trait instead of the method in this case. It is less generic, but a bit simpler:

trait SomeTrait[V] {
  def doSomething(msg: Message[V]): Message[V]
}

val someWork = new SomeTrait[Int] {
  def doSomething(msg: Message[Int]) = msg.copy(msg.elems.mapValues(_ + 1) 
}
Related