问题
I have the following code:
sealed trait Animal
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
trait Show[A] {
def show(a: A): String
}
class Processor[A](a: A) {
def print(implicit S: Show[A]): Unit = println(S.show(a))
}
implicit val showCat: Show[Cat] = c => s"Cat=${c.name}"
implicit val showDog: Show[Dog] = d => s"Dog=${d.name}"
val garfield = Cat("Garfield")
val odie = Dog("Odie")
val myPets = List(garfield, odie)
for (p <- myPets) {
val processor = new Processor(p)
processor.print // THIS FAILS AT THE MOMENT
}
Does anyone know of a nice way to get that line processor.print
working?
I can think of 2 solutions:
- pattern match the
p
in the for loop. - create an instance of
Show[Animal]
and pattern match it against all its subtypes.
But I'm wondering if there's a better way of doing this.
Thanks in advance!
回答1:
Compile error is
could not find implicit value for parameter S: Show[Product with Animal with java.io.Serializable]
You can make Animal
extend Product
and Serializable
sealed trait Animal extends Product with Serializable
https://typelevel.org/blog/2018/05/09/product-with-serializable.html
Also instead of defining implicit Show[Animal]
manually
implicit val showAnimal: Show[Animal] = {
case x: Cat => implicitly[Show[Cat]].show(x)
case x: Dog => implicitly[Show[Dog]].show(x)
// ...
}
you can derive Show
for sealed traits (having instances for descendants) with macros
def derive[A]: Show[A] = macro impl[A]
def impl[A: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
val typA = weakTypeOf[A]
val subclasses = typA.typeSymbol.asClass.knownDirectSubclasses
val cases = subclasses.map{ subclass =>
cq"x: $subclass => _root_.scala.Predef.implicitly[Show[$subclass]].show(x)"
}
q"""
new Show[$typA] {
def show(a: $typA): _root_.java.lang.String = a match {
case ..$cases
}
}"""
}
implicit val showAnimal: Show[Animal] = derive[Animal]
or Shapeless
implicit val showCnil: Show[CNil] = _.impossible
implicit def showCcons[H, T <: Coproduct](implicit
hShow: Show[H],
tShow: Show[T]
): Show[H :+: T] = _.eliminate(hShow.show, tShow.show)
implicit def showGen[A, C <: Coproduct](implicit
gen: Generic.Aux[A, C],
show: Show[C]
): Show[A] = a => show.show(gen.to(a))
or Magnolia
object ShowDerivation {
type Typeclass[T] = Show[T]
def combine[T](ctx: CaseClass[Show, T]): Show[T] = null
def dispatch[T](ctx: SealedTrait[Show, T]): Show[T] =
value => ctx.dispatch(value) { sub =>
sub.typeclass.show(sub.cast(value))
}
implicit def gen[T]: Show[T] = macro Magnolia.gen[T]
}
import ShowDerivation.gen
or Scalaz-deriving
@scalaz.annotation.deriving(Show)
sealed trait Animal extends Product with Serializable
object Show {
implicit val showDeriving: Deriving[Show] = new Decidablez[Show] {
override def dividez[Z, A <: TList, ShowA <: TList](tcs: Prod[ShowA])(
g: Z => Prod[A]
)(implicit
ev: A PairedWith ShowA
): Show[Z] = null
override def choosez[Z, A <: TList, ShowA <: TList](tcs: Prod[ShowA])(
g: Z => Cop[A]
)(implicit
ev: A PairedWith ShowA
): Show[Z] = z => {
val x = g(z).zip(tcs)
x.b.value.show(x.a)
}
}
}
For cats.Show with Kittens you can write just
implicit val showAnimal: Show[Animal] = cats.derived.semi.show
The thing is that garfield
and odie
in List(garfield, odie)
have the same type and it's Animal
instead of Cat
and Dog
. If you don't want to define instance of type class for parent type you can use list-like structure preserving types of individual elements, HList garfield :: odie :: HNil
.
For comparison deriving type classes in Dotty
How to access parameter list of case class in a dotty macro
回答2:
The most general solution is to just pack the typeclass instances in at the creation of myPets
, existentially
final case class Packaged[+T, +P](wit: T, prf: P)
type WithInstance[T, +P[_ <: T]] = Packaged[U, P[U]] forSome { type U <: T }
implicit def packageInstance[T, U <: T, P[_ <: T]]
(wit: U)(implicit prf: P[U])
: T WithInstance P
= Packaged(wit, prf)
val myPets = List[Animal WithInstance Show](garfield, odie)
for(Packaged(p, showP) <- myPets) {
implicit val showP1 = showP
new Processor(p).print // note: should be def print()(implicit S: Show[A]), so that this can be .print()
}
来源:https://stackoverflow.com/questions/61392874/use-the-lowest-subtype-in-a-typeclass