Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Behaviour of implicit search #22171

Unanswered
NPCRUS asked this question in General Question
Dec 9, 2024 · 1 comments · 6 replies
Discussion options

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].
You must be logged in to vote

Replies: 1 comment 6 replies

Comment options

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]
You must be logged in to vote
6 replies
Comment options

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

Comment options

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?

Comment options

@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

Comment options

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

Comment options

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
2 participants

AltStyle によって変換されたページ (->オリジナル) /