I always thought that context bounds and implicit parameter lists behaved exactly the same, but apparently not.
In the example below, I expect summon1[Int]
and summon2[Int]
to return the same type, but they don't. I expected summon2[Int]
to return a path dependent type, but instead it gives me a type projection. Why?
Welcome to the Ammonite Repl 2.2.0 (Scala 2.13.3 Java 11.0.2)
@ trait Foo[A] {
type B
def value: B
}
defined trait Foo
@ implicit def fooInt = new Foo[Int] {
override type B = String
override def value = "Hey!"
}
defined function fooInt
@ implicit def fooString = new Foo[String] {
override type B = Boolean
override def value = true
}
defined function fooString
@ def summon1[T](implicit f: Foo[T]) = f.value
defined function summon1
@ def summon2[T: Foo] = implicitly[Foo[T]].value
defined function summon2
@ summon1[Int]
res5: String = "Hey!"
@ summon2[Int]
res6: Foo[Int]#B = "Hey!"
@
The thing is primarily not in the difference of context bound vs. implicit parameter (
there shouldn't be any difference(*)), the thing is thatimplicitly
can break type of implicit foundhttps://typelevel.org/blog/2014/01/18/implicitly_existential.html
If you fix
summon2
using custom materializer this will work as expectedIt's intersting that
shapeless.the
doesn't helpAlso in Scala 2.13 you can use more general form of materializer (not specific for
Foo
) returning singleton type (like it's done in Scala 3)(*) Well, there is a difference that if you don't introduce parameter name
f
you can't refer to the typef.B
explicitly in the return type. And if you don't specify return type explicitly, as we can see such typef.B
can't be inferred because of the lack of a stable prefixf
(see also Aux-pattern usage compiles without inferring an appropriate type).