Implicit definition working for Seq but not for Set

元气小坏坏 提交于 2019-12-11 14:49:22

问题


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
}

回答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>


来源:https://stackoverflow.com/questions/23274033/implicit-definition-working-for-seq-but-not-for-set

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!