Suppose I've got an ADT and type class Foo like this:
sealed trait A
case class A1() extends A
case class A2() extends A
case class A3() extends A
trait Foo[X] { def foo(x: X): String; }
object Foo {
  implicit val a1foo = new Foo[A1] { def foo(a1: A1) = "A1" }
  implicit val a2foo = new Foo[A2] { def foo(a2: A2) = "A2" }
  implicit val a3foo = new Foo[A3] { def foo(a3: A3) = "A3" }
}  
Now I can write Foo[A] like that:
implicit val afoo = new Foo[A] {
  def foo(a: A) = a match {
    case a1 : A1 => a1foo.foo(a1)
    case a2 : A2 => a2foo.foo(a2)
    case a3 : A3 => a3foo.foo(a3)
  }
}
Unfortunately this code is too boilerplaty. Is it possible to get rid of all that boilerplate and derive Foo[A] automatically (perhaps with shapeless) ?
This afoo implicit is not only useless in the presence of the other three, but even bad, because it will fail with a MatchError on the new A {} value.
I don't see why you would need such instance of Foo[A] when you have implicits that cover all possible (valid) values of type A and afoo doesn't add anything. 
I can imagine that if you have a function
def foo(a: A)(implicit f: Foo[A]): String = f.foo(a)
Then, of course, neither of a1foo, a2foo or a3foo will fit. afoo will, so foo(A1()) will compile and work fine, but foo(new A {}) will also compile, and fail with a MatchError. By the way, if the call foo(new A {}) is present in code, it will compile with a warning about non-exhaustive match, but if it's not, it will compile silently.
So the solution to this is to modify foo, to take a more precise type:
def foo[X <: A](a: X)(implicit f: Foo[X]): String = f.foo(a)
Now foo(A1()) will compile and pick a1foo (same with A2 and A3), while foo(new A {}) just won't compile (because it shouldn't).
If you still want to have an instance of Foo[A] as a default case, you can change your code like this:
object Foo extends Foo_1 {
  implicit val a1foo: Foo[A1] = ...
  implicit val a2foo: Foo[A2] = ...
  implicit val a3foo: Foo[A3] = ...
}
trait Foo_1 {
  // this implicit has a lower priority:
  implicit val afoo: Foo[A] = new Foo[A] { def foo(a: A) = "A" }
}
Now foo(new A {}) or foo(A2(): A) will compile and both return "A".
P.S. by the way, it's recommended to write explicit type of implicits.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With