-
Couldn't load subscription status.
- Fork 1.1k
Behaviour of implicit search #22171
-
Hello folks, I was researching different cases of variance quirks in scala and I bumped into this one for which I so far wasn't able to produce any sensible explanation whatsoever, maybe somebody could help me here with explanation or perhaps it's a bug...
Here we have invariant Show[T] typeclass, which has been implicitly converted into contrvariant Show2[-T] typeclass. This variance difference causing an ambiguous implicit search error.
Interestingly this example(when givens are replaced with implicits) is compiling in scala2, therefore I thought that there are some changes in implicit resolution, but going through this multiple times didn't really help.
Another note is that results is the same for all stable major versions of scala3, I tested with: 3.3.3, 3.4.2, 3.5.2, 3.6.2-RC1
Since original Show[T] is invariant, I expect search to be able to narrow it down to one of the presented givens
trait Show[T] { def show(v: T): String } trait Show2[-T] { def show2(a: T): String } object Show2 { given [T](using show: Show[T]): Show2[T] with override def show2(a: T): String = show.show(a) } sealed trait A case object B extends A given Show[A] with def show(v: A): String = "A" given Show[B.type] with def show(v: B.type): String = "B" val t = summon[Show2[B.type]] // compiler error
and I get this error:
No best given instance of type ImplicitResolution.Show2[ImplicitResolution.B.type] was found for parameter x of method summon in object Predef.
I found:
ImplicitResolution.Show2.given_Show2_T[T](
/* ambiguous: both object given_Show_A in object ImplicitResolution and object given_Show_B_type in object ImplicitResolution match type ImplicitResolution.Show[T] */
summon[ImplicitResolution.Show[T]]
)
But both object given_Show_A in object ImplicitResolution and object given_Show_B_type in object ImplicitResolution match type ImplicitResolution.Show[T].
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 1 comment 6 replies
-
summon[Show2[B.type]]
is ambiguous, which becomes evident when compiling
val t1: Show2[B.type] = Show2.given_Show2_T[B.type] val t2: Show2[B.type] = Show2.given_Show2_T[A]
Beta Was this translation helpful? Give feedback.
All reactions
-
To my understanding the compiler simply doesn't know what type to substitute for T while trying to invoke Show2.given_Show2_T - whether this should be B.type or A, because both are possible here
Beta Was this translation helpful? Give feedback.
All reactions
-
Thinking out loud now.
I assume the code that worked with scala 2 was something like
trait Show[T] { def show(v: T): String } trait Show2[-T] { def show2(a: T): String } object Show2 { implicit def given_Show2_T[T](implicit show: Show[T]): Show2[T] = new Show2[T] { override def show2(a: T): String = show.show(a) } } sealed trait A case object B extends A object Test { implicit lazy val given_Show_A: Show[A] = new Show[A] { def show(v: A): String = "A" } implicit lazy val given_Show_B_type: Show[B.type] = new Show[B.type] { def show(v: B.type): String = "B" } val t = implicitly[Show2[B.type]] }
Compiling this with scala-cli compile Show.scala -S 2.13.15 -Vprint:typer --server=false
prints:
package <empty> { abstract trait Show[T] extends scala.AnyRef { def show(v: T): String }; abstract trait Show2[-T] extends scala.AnyRef { def show2(a: T): String }; object Show2 extends scala.AnyRef { def <init>(): Show2.type = { Show2.super.<init>(); () }; implicit def given_Show2_T[T](implicit show: Show[T]): Show2[T] = { final class $anon extends AnyRef with Show2[T] { def <init>(): <$anon: Show2[T]> = { $anon.super.<init>(); () }; override def show2(a: T): String = show.show(a) }; new $anon() } }; sealed abstract trait A extends scala.AnyRef; case object B extends AnyRef with A with Product with Serializable { def <init>(): B.type = { B.super.<init>(); () }; override <synthetic> def productPrefix: String = "B"; <synthetic> def productArity: Int = 0; <synthetic> def productElement(x1ドル: Int): Any = x$1 match { case _ => scala.runtime.Statics.ioobe[Any](x$1) }; override <synthetic> def productIterator: Iterator[Any] = scala.runtime.ScalaRunTime.typedProductIterator[Any](B.this); <synthetic> def canEqual(x1ドル: Any): Boolean = x$1.$isInstanceOf[B.type](); override <synthetic> def hashCode(): Int = 66; override <synthetic> def toString(): String = "B"; <synthetic> private def writeReplace(): Object = new scala.runtime.ModuleSerializationProxy(classOf[B$]) }; object Test extends scala.AnyRef { def <init>(): Test.type = { Test.super.<init>(); () }; implicit <stable> <accessor> lazy val given_Show_A: Show[A] = { final class $anon extends AnyRef with Show[A] { def <init>(): <$anon: Show[A]> = { $anon.super.<init>(); () }; def show(v: A): String = "A" }; new $anon() }; implicit <stable> <accessor> lazy val given_Show_B_type: Show[B.type] = { final class $anon extends AnyRef with Show[B.type] { def <init>(): <$anon: Show[B.type]> = { $anon.super.<init>(); () }; def show(v: B.type): String = "B" }; new $anon() }; private[this] val t: Show2[B.type] = scala.Predef.implicitly[Show2[B.type]](Show2.given_Show2_T[B.type](Test.this.given_Show_B_type)); <stable> <accessor> def t: Show2[B.type] = Test.this.t } }
So maybe now the question is actually why scala 2 doesn't report an ambiguity here?
Beta Was this translation helpful? Give feedback.
All reactions
-
@prolativ sorry for late response, that's good point about scala2. But getting back to my question: I do understand what does ambiguity in implicit search mean, that it cannot choose between those two. But what I would like to understand is: some sort of reasoning about how does the implicit search implementation reaches the state where it cannot decide in this particular example, taking into account the variance of typeclasses,
because if I change example to T => Show[-T] => Show2[T] then it hapily compiles. And taking into account all the available information on variance and implicit search rules, I cannot deduce why original example hit ambiguity and this one doesn't
Beta Was this translation helpful? Give feedback.
All reactions
-
Not sure if I get how exactly you modified the example to make it work. Could you paste the entire compile-ready snippet here?
Implicit search is quite a convoluted thing lying in some of the darkest corners of the compiler (https://github.com/scala/scala3/blob/main/compiler/src/dotty/tools/dotc/typer/Implicits.scala). The same is true for type inference (BTW implicit resolution is sometimes called term inference) and as these two might interfere with each other, sometimes it's really hard to tell why things got inferred in this way and not the other.
Just to be clear: you might try to compile my example with scala 3, but then the compiler also reports an ambiguity. And it's hard to tell at which point scala 3's behaviour diverges from scala 2 because the ambiguity occurs already in scala 3.0.0
Beta Was this translation helpful? Give feedback.
All reactions
-
here it is:
object Test { trait Show[-T] { def show(v: T): String } trait Show2[T] { def show2(a: T): String } object Show2 { given [T](using show: Show[T]): Show2[T] with override def show2(a: T): String = show.show(a) } sealed trait A case object B extends A given Show[A] with def show(v: A): String = "A" given Show[B.type] with def show(v: B.type): String = "B" val t = summon[Show2[B.type]] }
Also I think it's not such a rare case, I bumped into it while migrating from scala2 to scala3. and such hierarchy represents the integration layer between not such a rare two libraries, which is circe and pekko. Then the question would be, how can we land at the right explanation for this? Maybe you can point me into some direction where I can continue my investigation?
Beta Was this translation helpful? Give feedback.