So I've made some utility classes and implicit conversions for them. However, it works fine when converting from a Seq but not from a Set, although the code is the same, and those two traits seem rather similar at first sight. What could be the problem, and how would I fix it?
import scala.collection.mutable.HashMap
trait Indexed[T] {
def index: T
}
class IndexMap[T, V <: Indexed[T]] extends HashMap[T, V] {
override def put(key: T, value: V): Option[V] = {
require(key == value.index)
super.put(key, value)
}
final def put(value: V): Option[V] = put(value.index, value)
}
trait Named extends Indexed[String] {
final def index = name
def name: String
}
type NameMap[T <: Named] = IndexMap[String, Named]
This works fine:
implicit def seq2IndexMap[T, V <: Indexed[T]](s: Seq[V]): IndexMap[T,V] = {
val ret = new IndexMap[T,V]();
s.foreach(v => ret.put(v))
ret
}
However, this fails to compile with type mismatch; found : scala.collection.immutable.Set[Program.ClassData] required: Common.NameMap[Program.ClassData] (which expands to) Common.IndexMap[String,Common.Named]
implicit def set2IndexMap[T, V <: Indexed[T]](s: Set[V]): IndexMap[T,V] = {
val ret = new IndexMap[T,V]();
s.foreach(v => ret.put(v))
ret
}
On input:
val c = Class("Test", Set(ClassData("data1", null), ClassData("data2", null)))
Where ClassData
extends Named
.
I'm using Scala 2.10.
Edit:
The simplified definitions of Class and ClassData for convenience:
case class ClassData(name: String, p: Any) extends Named
case class Class(n: String, data: NameMap[ClassData])
Edit 2:
Ok, we found the problem. It was indeed because Set
is invariant (which I don't understand why).
When I wrote Set(ClassData("data1", null))
, it made a Set[ClassData]
, which could not be interpreted as a Set[Named]
, whereas it worked with Seq
because Seq
is covariant.
Interestingly enough, Scala doesn't have any problem when we explicitly call the conversion:
val c = Class("Test", set2IndexMap((Set(ClassData("data1", null), ClassData("data2", null))))
I think Scala is able, in this case, to infer which type of Set
to infer. In my opinion, this shows how Scala can be too complex. If I also had an error with the explicit version, I could have immediately seen what was wrong with the implicit conversion. I feel like too many things happen behind the scenes, and ultimately you have to know them or you'll get stuck with problems like this.
A solution was to explicitly state the type of the set:
val c = Class("Test", Set[Named](ClassData("data1", null), ClassData("data2", null)))
A better solution was to make the implicit conversion work for Iterable
or even Traversable
, which are super traits of both Seq
and Set
, and are covariant (although Set
is not, while being covariant as a Iterable
).
implicit def set2IndexMap[T, V <: Indexed[T]](s: Traversable[V]): IndexMap[T,V] = {
val ret = new IndexMap[T,V]();
s.foreach(v => ret.put(v))
ret
}
HashMap
was imported but not shown? – WiraClass
(defined in java.lang). – Wira