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 cla
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
}
0__'s answer (use the implicit evidence argument to turn bar
into the right type) is the answer I'd give to the specific question you asked (although I'd suggest not using implicitly
if you've got the implicit argument sitting right there).
It's worth noting that the situation you're describing sounds like it might be a good use case for ad-hoc polymorphism via type classes, however. Say for example that we've got the following setup:
trait Foo
case class Bar[F <: Foo](foo: F)
case class Grill[B <: Bar[_]](bar: B)
And a type class, along with some convenience methods for creating new instances and for pimping a readField
method onto any type that has an instance in scope:
trait Readable[A] { def field(a: A): Int }
object Readable {
def apply[A, B: Readable](f: A => B) = new Readable[A] {
def field(a: A) = implicitly[Readable[B]].field(f(a))
}
implicit def enrich[A: Readable](a: A) = new {
def readField = implicitly[Readable[A]].field(a)
}
}
import Readable.enrich
And a couple of instances:
implicit def barInstance[F <: Foo: Readable] = Readable((_: Bar[F]).foo)
implicit def grillInstance[B <: Bar[_]: Readable] = Readable((_: Grill[B]).bar)
And finally a readable Foo
:
case class MyFoo(x: Int) extends Foo
implicit object MyFooInstance extends Readable[MyFoo] {
def field(foo: MyFoo) = foo.x
}
This allows us to do the following, for example:
scala> val readableGrill = Grill(Bar(MyFoo(11)))
readableGrill: Grill[Bar[MyFoo]] = Grill(Bar(MyFoo(11)))
scala> val anyOldGrill = Grill(Bar(new Foo {}))
anyOldGrill: Grill[Bar[java.lang.Object with Foo]] = Grill(Bar($anon$1@483457f1))
scala> readableGrill.readField
res0: Int = 11
scala> anyOldGrill.readField
<console>:22: error: could not find implicit value for evidence parameter of
type Readable[Grill[Bar[java.lang.Object with Foo]]]
anyOldGrill.readField
^
Which is what we want.
This is not an answer to the question, but to show that the 'type constraint' is really just an implicit conversion:
Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_33).
Type in expressions to have them evaluated.
Type :help for more information.
scala> trait A { def test() {} }
defined trait A
scala> class WhatHappens[T] { def test(t: T)(implicit ev: T <:< A) = t.test() }
defined class WhatHappens
scala> :javap -v WhatHappens
...
public void test(java.lang.Object, scala.Predef$$less$colon$less);
Code:
Stack=2, Locals=3, Args_size=3
0: aload_2
1: aload_1
2: invokeinterface #12, 2; //InterfaceMethod scala/Function1.apply:(Ljava/lang/Object;)Ljava/lang/Object;
7: checkcast #14; //class A
10: invokeinterface #17, 1; //InterfaceMethod A.test:()V
15: return
...
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this LWhatHappens;
0 16 1 t Ljava/lang/Object;
0 16 2 ev Lscala/Predef$$less$colon$less;
...