Use the lowest subtype in a typeclass?

一世执手 提交于 2020-07-23 08:48:04

问题


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:

  1. pattern match the p in the for loop.
  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!