Question

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?

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top