11

I am designing an API using type classes in some cases however I have encountered a problem with implicit resolution. As shown below, if there is an implicit object for type A but an object of type B extends A is passed to the method, then an implicit object cannot be found. Is there a way to make this work or do callers have to put implicit objects into scope for each subclass?

Here is an example:

class A
class B extends A

class T[+X]

object T {
  implicit object TA extends T[A]
}

def call[X:T](x:X) = println(x)

// compiles
call(new A)
// doesn't compile
call(new B)

var a = new A
// compiles
call(a)

a = new B
// compiles
call(a)

val b = new B
// doesn't compile
call(b)

This fails to compile with the following output:

/private/tmp/tc.scala:16: error: could not find implicit value for evidence parameter of type this.T[this.B]
call(new B)
    ^
/private/tmp/tc.scala:28: error: could not find implicit value for evidence parameter of type this.T[this.B]
call(b)
Jesse Eichar
  • 1,081
  • 1
  • 11
  • 11
  • I also tried changing the definition of call to: def call[X,X2 <: X](x:X2)(implicit x2:T[X]) = println(x) And that did not help – Jesse Eichar Oct 06 '10 at 06:41

3 Answers3

8

The call call(new B) means call[B](new B)(tB) such that tb is of type T[B] or subclass of it. (A method that expects argument of type T can only expect T or subclass of T, e.g., def foo(s: String) cannot be called with argument of type Any). T[A] is not a subtype of T[B]

To fix, you can change T to be defined T[-X]. This means that the compiler will consider T[A] to be a subtype of T[B]

IttayD
  • 28,271
  • 28
  • 124
  • 178
  • Problem is, even if you also define `implicit object TB extends T[B]`, `implicitly[T[B]] == T.TA`. http://lampsvn.epfl.ch/trac/scala/ticket/2509 – retronym Oct 06 '10 at 22:40
  • sounds reasonable, because TA is then more specific than TB and TA is-a TB so should be usable where T[B] is. if you want the implicit to match the type exactly, define T as T[X] – IttayD Oct 07 '10 at 04:27
4

The following works fine:

scala> def call[X](x: X)(implicit evidence: T[X]<:<T[X])  = println(x)
call: [X](x: X)(implicit evidence: <:<[T[X],T[X]])Unit

scala> call(new A)
line0$object$$iw$$iw$A@1d869b2

scala> call(new B)
line2$object$$iw$$iw$B@b3a5d1

scala> val b = new B
b: B = B@30e4a7

scala> call(b)
line2$object$$iw$$iw$B@30e4a7

In your case compilation fails, because def call[X:T](x:X) = println(x) is treated as call: [X](x: X)(implicit evidence$1: T[X])Unit. In order to pass the subtype, you may use generalized type constraints.

Vasil Remeniuk
  • 20,519
  • 6
  • 71
  • 81
  • Is there a way to actually get evidence$1? For example suppose T was a function and x was the parameter. I cannot call evidence(x) – Jesse Eichar Oct 06 '10 at 09:33
  • Why you cannot? You can call it by name `evidence$1`, like any other [implicit] parameter. – Vasil Remeniuk Oct 06 '10 at 09:57
  • If you have several type parameters on the method, there will be evidence$1, evidence$2 etc: `[X,Z](x: X)(implicit evidence$1: T[X],implicit evidence$2: T[Z])Unit` – Vasil Remeniuk Oct 06 '10 at 10:00
  • My question did not quite capture my needs. I loved learning this tip but my actual needs turned out to be: trait T[X] extends Function1[X,Boolean]. and call is: def call[X](x:X)(implicit x2:T[X]) = x2(x). Turned out using T[-X] worked for me. Cool to learn about the <:< though. – Jesse Eichar Oct 06 '10 at 19:20
  • 2
    `def foo[A: M] = { val ma = implicitly[M[A]; ... }` – retronym Oct 06 '10 at 22:42
  • @jesse-eichar If T[-X] worked out for you, why not accept the answer I provided? – IttayD Oct 07 '10 at 07:06
2

Try this:

object T {
  implicit def TA[X <: A] = new T[X]
}

import T._

or simply:

implicit def TA[X <: A] = new T[X]
Tom Crockett
  • 30,818
  • 8
  • 72
  • 90