I have the following code defining a type class.
trait Foo[T] {
def toFoo(x: T): String
}
trait Foos {
def toFoo[T](f: T => String): Foo[T] = new Foo[T] {
def toFoo(x: T): String = f(x)
}
}
object Foo extends Foos {
def toFoo[A: Foo](a: A) = implicitly[Foo[A]].toFoo(a)
implicit def AToFoo: Foo[A] = toFoo { c =>
"A"
}
implicit def BToFoo[T]: Foo[B] = toFoo { c =>
"B"
}
implicit def ListToFoo[T: Foo]: Foo[List[T]] = toFoo { c =>
c.map(toFoo(_)).
}
}
class A
class B extends A
Now if I have if I do toFoo(List(new A, new B)
I get List("A", "A")
instead of List("A", "B")
. How can I ensure that the BtoFoo
method is used rather than AToFoo
for classes with type B
?
implicit resolution is a purely compile-time mechanism. The easy way to distinguish between subtypes here is to match inside the implicit. Remove BToFoo
altogether and have instead the A
version deal with both cases
implicit val AIsFoo : Foo[A] = toFoo {
case b: B => "B"
case a: A => "A"
}
Of course, it is also possible to delegate to a method for part of the hierarchy. You may delegate to a method in A
, overidde in B
, and still have the typeclass work for List, where you cannot add a method.
You may also consider declaring Foo
contravariant.