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

前端 未结 2 2002
失恋的感觉
失恋的感觉 2021-02-04 13:27

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

W

相关标签:
2条回答
  • 2021-02-04 13:42

    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.

    0 讨论(0)
  • 2021-02-04 13:48

    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]]
    }
    
    0 讨论(0)
提交回复
热议问题