Here is a simple setup with two traits, a class with a covariant type parameter bounded by the previous traits, and a second class with a type parameter bounded by the other class. For both classes, a particular method is available (via implicit evidence) only if one of the two traits underlies the type parameter. This compiles fine:
trait Foo
trait ReadableFoo extends Foo {def field: Int}
case class Bar[+F <: Foo](foo: F) {
def readField(implicit evidence: F <:< ReadableFoo) = foo.field
}
case class Grill[+F <: Foo, +B <: Bar[F]](bar: B) {
def readField(implicit evidence: F <:< ReadableFoo) = bar.readField
}
However, since Bar
is covariant in F
, I shouldn't need the F
parameter in Grill
. I should just require that B
is a subtype of Bar[ReadableFoo]
. This, however, fails:
case class Grill[+B <: Bar[_]](bar: B) {
def readField(implicit evidence: B <:< Bar[ReadableFoo]) = bar.readField
}
with the error:
error: Cannot prove that Any <:< this.ReadableFoo.
def readField(implicit evidence: B <:< Bar[ReadableFoo]) = bar.readField
Why is the implicit evidence not being taken into account?
The call bar.readField
is possible because the evidence instance <:<
allows an implicit conversion from B
to Bar[ReadableFoo]
.
The problem I think that to call readField
you need a successive evidence parameter F <:< ReadableFoo
. So my guess is, the compiler doesn't fully substitute the type parameter of Bar
in the first search stage of the implicit resolution (because to find readField
, it just requires any Bar
in the first place). And then it chokes on the second implicit resolution, because there is no form of 'backtracking' as far as I know.
Anyway. The good thing is, you know more than the compiler and you can engage the conversion explicitly, either by using the apply
method of <:<
, or by using the helper method implicitly
:
case class Grill[+B <: Bar[_]](bar: B) {
def readField(implicit evidence: B <:< Bar[ReadableFoo]) = evidence(bar).readField
}
case class Grill[+B <: Bar[_]](bar: B) {
def readField(implicit evidence: B <:< Bar[ReadableFoo]) =
implicitly[Bar[ReadableFoo]](bar).readField
}
There is another possibility which might be the cleanest, as it doesn't rely on the implementation of <:<
which might be a problem as @Kaito suggests:
case class Grill[+B <: Bar[_]](bar: B) {
def readField(implicit evidence: B <:< Bar[ReadableFoo]) =
(bar: Bar[ReadableFoo]).readField
}