Question

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
}
Was it helpful?

Solution

Infamously, Set is invariant in its type parameter.

That seems to make the implicit not apply?

Maybe V is not inferred correctly. Sometimes it likes to infer Nothing.

(Posting a complete minimization would help someone help you.)

I'll try to decipher the -Ytyper-debug when I get a chance, but FTR:

Succeeding on Seq:

|    |    |    solving for (A: ?A)
|    |    |    |-- seq2IndexMap BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value b  in Test) implicits disabled
|    |    |    |    [adapt] [T, V <: Code.this.Indexed[T]](s: Seq[V])Code.this.IndexM... adapted to [T, V <: Code.this.Indexed[T]](s: Seq[V])Code.this.IndexM...
|    |    |    |    \-> (s: Seq[V])nosetconvert.Test.IndexMap[T,V]
|    |    |    solving for (T: ?T, V: ?V)
|    |    |    [adapt] seq2IndexMap adapted to [T, V <: Code.this.Indexed[T]](s: Seq[V])Code.this.IndexM... based on pt Seq[nosetconvert.Test.ClassData] => nosetconvert.Test.NameMap[nosetconvert.Test.ClassData]
|    |    |    |-- [T, V <: Code.this.Indexed[T]](s: Seq[V])Code.this.IndexM... : pt=nosetconvert.Test.NameMap[nosetconvert.Test.ClassData] BYVALmode-EXPRmode (silent: value b  in Test) implicits disabled
|    |    |    |    \-> nosetconvert.Test.IndexMap[String,nosetconvert.Test.Named]
|    |    |    [adapt] [A](elems: A*)CC[A] adapted to [T, V <: Code.this.Indexed[T]](s: Seq[V])Code.this.IndexM... based on pt nosetconvert.Test.NameMap[nosetconvert.Test.ClassData]
|    |    |    \-> nosetconvert.Test.IndexMap[String,nosetconvert.Test.Named]

Failing on Set:

|    |    |    solving for (A: ?A)
|    |    |    |-- set2IndexMap BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value c  in Test) implicits disabled
|    |    |    |    [adapt] [T, V <: Code.this.Indexed[T]](s: Set[V])Code.this.IndexM... adapted to [T, V <: Code.this.Indexed[T]](s: Set[V])Code.this.IndexM...
|    |    |    |    \-> (s: Set[V])nosetconvert.Test.IndexMap[T,V]
|    |    |    |-- nosetconvert.this.Test.set2IndexMap BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value c  in Test) implicits disabled
|    |    |    |    [adapt] [T, V <: Code.this.Indexed[T]](s: Set[V])Code.this.IndexM... adapted to [T, V <: Code.this.Indexed[T]](s: Set[V])Code.this.IndexM...
|    |    |    |    \-> (s: Set[V])nosetconvert.Test.IndexMap[T,V]
|    |    |    \-> <error>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top