Implicit definition working for Seq but not for Set
Asked Answered
C

1

0

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
}
Castano answered 24/4, 2014 at 16:0 Comment(3)
Your first code block does not compile. Which HashMap was imported but not shown?Wira
It's scala.collection.mutable.HashMap, since you can see I mutate it.Castano
By the way, you should avoid core Java library names such as Class (defined in java.lang).Wira
G
1

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>
Godden answered 24/4, 2014 at 17:21 Comment(3)
What do you mean by complete minimization?Castano
@LP_ I just meant something that compiles.Godden
Thanks, the problem was indeed invariance (cf: edited question).Castano

© 2022 - 2024 — McMap. All rights reserved.