What is the purpose of the emptyCoproduct and coproduct methods of the TypeClass trait in Shapeless

半城伤御伤魂 提交于 2019-12-03 10:32:04

问题


It is not fully clear to me what is the purpose of the emptyCoProduct and coproduct methods of the TypeClass trait in Shapeless.

When would one use the TypeClass trait instead of the ProductTypeClass?

What are some examples of ways those two methods would be implemented?


回答1:


Suppose I've got a simple type class:

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

And some instances:

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

And a case class:

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

And an ADT:

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

I can define a ProductTypeClass instance for my type class:

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

And use it like this:

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

But!

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

This is a problem—it makes our automated type class instance derivation pretty much useless for ADTs. Fortunately we can use TypeClass instead:

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

And then:

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

All the other stuff above still works as well.

To sum up: Shapeless's Coproduct is a kind of abstraction over ADTs, and in general you should provide instances of TypeClass for your type classes instead of just ProductTypeClass whenever possible.




回答2:


As of shapeless 2.3.2, the above example doesn't seem to compile. here's the updated one for future reference:

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


来源:https://stackoverflow.com/questions/25517069/what-is-the-purpose-of-the-emptycoproduct-and-coproduct-methods-of-the-typeclass

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