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