Shapeless中TypeClass特征的emptyCoproduct和coproduct方法的目的是什么?

时间:2014-08-27 00:15:12

标签: scala typeclass shapeless type-level-computation

我不完全清楚Shapeless中emptyCoProduct特征coproductTypeClass方法的目的是什么。

什么时候会使用TypeClass特征而不是ProductTypeClass

这两种方法的实施方式有哪些例子?

2 个答案:

答案 0 :(得分:19)

假设我有一个简单的类型类:

trait Weight[A] { def apply(a: A): Int }

object Weight {
  def apply[A](f: A => Int) = new Weight[A] { def apply(a: A) = f(a) }
}

还有一些例子:

implicit val stringWeight: Weight[String] = Weight(_.size)
implicit def intWeight: Weight[Int] = Weight(identity)

案例类:

case class Foo(i: Int, s: String)

ADT:

sealed trait Root
case class Bar(i: Int) extends Root
case class Baz(s: String) extends Root

我可以为我的类型类定义ProductTypeClass实例:

import shapeless._

implicit object WeightTypeClass extends ProductTypeClass[Weight] {
  def emptyProduct: Weight[HNil] = Weight(_ => 0)
  def product[H, T <: HList](hw: Weight[H], tw: Weight[T]): Weight[H :: T] =
    Weight { case (h :: t) => hw(h) + tw(t) }
  def project[F, G](w: => Weight[G], to: F => G, from: G => F): Weight[F] =
    Weight(f => w(to(f)))
}

并像这样使用它:

scala> object WeightHelper extends ProductTypeClassCompanion[Weight]
defined object WeightHelper

scala> import WeightHelper.auto._
import WeightHelper.auto._

scala> implicitly[Weight[Foo]]
res0: Weight[Foo] = Weight$$anon$1@4daf1b4d

scala> implicitly[Weight[Bar]]
res1: Weight[Bar] = Weight$$anon$1@1cb152bb

scala> implicitly[Weight[Baz]]
res2: Weight[Baz] = Weight$$anon$1@74930887

但是!

scala> implicitly[Weight[Root]]
<console>:21: error: could not find implicit value for parameter e: Weight[Root]
              implicitly[Weight[Root]]
                        ^

这是一个问题 - 它使我们的自动类型类实例派生对于ADT几乎无用。幸运的是,我们可以使用TypeClass代替:

implicit object WeightTypeClass extends TypeClass[Weight] {
  def emptyProduct: Weight[HNil] = Weight(_ => 0)
  def product[H, T <: HList](hw: Weight[H], tw: Weight[T]): Weight[H :: T] =
    Weight { case (h :: t) => hw(h) + tw(t) }
  def project[F, G](w: => Weight[G], to: F => G, from: G => F): Weight[F] =
    Weight(f => w(to(f)))
  def emptyCoproduct: Weight[CNil] = Weight(_ => 0)
  def coproduct[L, R <: Coproduct]
    (lw: => Weight[L], rw: => Weight[R]): Weight[L :+: R] = Weight {
      case Inl(h) => lw(h)
      case Inr(t) => rw(t)
    }
}

然后:

scala> object WeightHelper extends TypeClassCompanion[Weight]
defined object WeightHelper

scala> import WeightHelper.auto._
import WeightHelper.auto._

scala> implicitly[Weight[Root]]
res0: Weight[Root] = Weight$$anon$1@7bc44e19

上述所有其他内容仍然有用。

总结一下:无形的Coproduct是对ADT的一种抽象,通常你应该为你的类型类提供TypeClass的实例而不仅仅是ProductTypeClass只要有可能。

答案 1 :(得分:3)

从无形2.3.2开始,上面的例子似乎没有编译。这里有更新的版本供将来参考:

import shapeless._
import shapeless.test._

trait Weight[A] { def apply(a: A): Int }

object Weight {
  def apply[A](f: A => Int) = new Weight[A] { def apply(a: A) = f(a) }
}

case class Foo(i: Int, s: String)

sealed trait Root
case class Bar(i: Int) extends Root
case class Baz(s: String) extends Root

object Base {
  implicit val stringWeight: Weight[String] = Weight(_.size)
  implicit def intWeight: Weight[Int] = Weight(identity)
}

object ProductTC {
  object WeightHelper extends ProductTypeClassCompanion[Weight] {
    object typeClass extends ProductTypeClass[Weight] {
      def emptyProduct: Weight[HNil] = Weight(_ => 0)
      def product[H, T <: HList](hw: Weight[H], tw: Weight[T]): Weight[H :: T] =
        Weight { case (h :: t) => hw(h) + tw(t) }
      def project[F, G](w: => Weight[G], to: F => G,from: G => F): Weight[F] =
        Weight(f => w(to(f)))
    }
  }

  import Base._
  import WeightHelper._

  implicitly[Weight[Foo]]
  implicitly[Weight[Bar]]
  implicitly[Weight[Baz]]

  illTyped("implicitly[Weight[Root]]")
}

object TC {
  object WeightTypeClass extends TypeClassCompanion[Weight] {
    object typeClass extends TypeClass[Weight] {
      def emptyProduct: Weight[HNil] = Weight(_ => 0)
      def product[H, T <: HList](hw: Weight[H], tw: Weight[T]): Weight[H :: T] =
        Weight { case (h :: t) => hw(h) + tw(t) }
      def project[F, G](w: => Weight[G], to: F => G, from: G => F): Weight[F] =
        Weight(f => w(to(f)))
      def emptyCoproduct: Weight[CNil] = Weight(_ => 0)
      def coproduct[L, R <: Coproduct]
        (lw: => Weight[L], rw: => Weight[R]): Weight[L :+: R] = Weight {
        case Inl(h) => lw(h)
        case Inr(t) => rw(t)
      }
    }
  }

  import Base._
  import WeightTypeClass._

  implicitly[Weight[Root]]
}