Say I have a 'wide' sealed class hierarchy:
sealed trait Alphabet
case class A(word: String) extends Alphabet
...
case class Z(word: String) extends Alphabet
And say I have a type class instance defined for each child class in the hierarchy:
trait SwearWordFinder[T <: Alphabet] {
def isSwearWord(x: T): Boolean
}
val swearWordFinderA = new SwearWordFinder[A] { ... }
...
val swearWordFinderZ = new SwearWordFinder[Z] { ... }
Is there a way I can define a type class instance for the Alphabet
trait itself without having to implement it by pattern matching (as below)?
def isSwearWord(x: Alphabet): Boolean = x match {
case a: A => swearWordFinderA.isSwearWord(a)
...
case z: Z => swearWordFinderZ.isSwearWord(z)
}
You can represent Alphabet
as a Shapeless Coproduct
of A :+: B :+: ... :+: Z :+: CNil
, so if you have SwearWordFinder
instances for A
, B
, ... and define instances for CNil
and :+:
you can get a SwearWordFinder[Alphabet]
using its generic representation.
import shapeless._
trait SwearWordFinder[T] {
def isSwearWord(x: T): Boolean
}
object SwearWordFinder extends SwearWordFinder0 {
implicit def apply[T](implicit swf: SwearWordFinder[T]): SwearWordFinder[T] = swf
implicit val cnilSwearWordFinder: SwearWordFinder[CNil] =
new SwearWordFinder[CNil] {
def isSwearWord(t: CNil): Boolean = false
}
implicit def coproductConsSwearWordFinder[L, R <: Coproduct](implicit
lSwf: SwearWordFinder[L],
rSwf: SwearWordFinder[R]
): SwearWordFinder[L :+: R] =
new SwearWordFinder[L :+: R] {
def isSwearWord(t: L :+: R): Boolean =
t.eliminate(lSwf.isSwearWord, rSwf.isSwearWord)
}
}
trait SwearWordFinder0 {
implicit def genericSwearWordFinder[T, G](implicit
gen: Generic.Aux[T, G],
swf: Lazy[SwearWordFinder[G]]
): SwearWordFinder[T] =
new SwearWordFinder[T] {
def isSwearWord(t: T): Boolean = swf.value.isSwearWord(gen.to(t))
}
}
Now some instances for our letters :
sealed trait Alphabet extends Product with Serializable
object Alphabet {
final case class A(word: String) extends Alphabet
final case class Z(word: String) extends Alphabet
}
implicit val swfA = new SwearWordFinder[Alphabet.A] {
def isSwearWord(a: Alphabet.A) = a.word == "apple"
}
implicit val swfZ = new SwearWordFinder[Alphabet.Z] {
def isSwearWord(z: Alphabet.Z) = z.word == "zebra"
}
And now we can get a SwearWordFinder[Alphabet]
:
def isBadWord[T](t: T)(implicit swfT: SwearWordFinder[T]): Boolean =
swfT.isSwearWord(t)
val a1: Alphabet = Alphabet.A("apple")
val z2: Alphabet = Alphabet.Z("zorro")
val z3: Alphabet = Alphabet.Z("zebra")
isBadWord(a1) // true
isBadWord(z2) // false
isBadWord(z3) // true
Like I mentioned in my comment: beware of SI-7046. Your Alphabet
AST needs to be in a project on which the project with SwearWordFinder
depends or in a package which will be compiled before the package with the type class derivation for Alphabet
.
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