【问题标题】:Use the lowest subtype in a typeclass?使用类型类中的最低子类型?
【发布时间】:2020-04-23 17:02:40
【问题描述】:

我有以下代码:

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
}

有没有人知道让processor.print 工作的好方法?

我能想到 2 个解决方案:

  1. 模式匹配for循环中的p
  2. 创建一个Show[Animal] 的实例并将其与它的所有子类型进行模式匹配。

但我想知道是否有更好的方法。

提前致谢!

【问题讨论】:

    标签: scala typeclass deriving


    【解决方案1】:

    编译错误是

    could not find implicit value for parameter S: Show[Product with Animal with java.io.Serializable]
    

    你可以让Animal扩展ProductSerializable

    sealed trait Animal extends Product with Serializable
    

    https://typelevel.org/blog/2018/05/09/product-with-serializable.html

    也不是手动定义隐式Show[Animal]

    implicit val showAnimal: Show[Animal] = {
      case x: Cat => implicitly[Show[Cat]].show(x)
      case x: Dog => implicitly[Show[Dog]].show(x)
      // ...
    }
    

    您可以使用macros 为密封特征(具有后代实例)派生Show

    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]
    

    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))
    

    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
    

    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)
        }
      }
    }
    

    对于cats.ShowKittens,您可以只写

    implicit val showAnimal: Show[Animal] = cats.derived.semi.show
    

    问题是List(garfield, odie) 中的garfieldodie 具有相同的类型,并且是Animal 而不是CatDog。如果您不想为父类型定义类型类的实例,您可以使用类似列表的结构来保留单个元素的类型,HListgarfield :: odie :: HNil


    用于比较 Dotty 中的派生类型类

    How to access parameter list of case class in a dotty macro

    【讨论】:

      【解决方案2】:

      最通用的解决方案是在创建 myPets 时将 typeclass 实例打包,存在

      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()
      }
      

      【讨论】:

      • 不错的方法。遗憾的是,这在 Scala 3 中不起作用(因为缺少存在类型)。只是好奇,名字witprf 是什么意思?
      • @DmytroMitin witness 和 proof。您有一些“真实价值”类型T(见证人)以及一些属性P 的“证明”或“证词”,例如Showability。这将在 Scala 3 中正常工作,并进行一些调整。 sealed trait WithInstance[T, +P[_ &lt;: T]] { type U &lt;: T; val wit: U; val prf: P[U] }; object WithInstance { implicit def apply[T, V &lt;: T, P[_ &lt;: T]](wit0: V)(implicit prf0: P[V]): T WithInstance P = new WithInstance[T, P] { type U = V; val wit = wit0; val prf = prf0 }; def unapply[T, P[_ &lt;: T]](wi: T WithInstance P): Some[(wi.U, P[wi.U])] = Some((wi.wit, wi.prf)) }
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-30
      • 2021-10-22
      • 2023-03-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多