【问题标题】:Define a Typeclass for Shapeless Records为无形记录定义一个类型类
【发布时间】:2015-03-06 01:59:47
【问题描述】:

我正在尝试学习 Shapeless,并且我想定义一个将无形记录实例相加的幺半群。请注意,我使用的是 algebird monoids(不是 scalaz),但我确信它们非常相似。这是我想做的一个例子:

val result = Monoid.sum(
  ('a ->> 1) :: ('b ->> 1) :: HNil,
  ('a ->> 4) :: ('b ->> 3) :: HNil,
  ('a ->> 2) :: ('b ->> 6) :: HNil)
// result should be: ('a ->> 7) :: ('b ->> 10) :: HNil

我想出了如何为HList编写monoid实例,如下:

  implicit val HNilGroup: Group[HNil] = new ConstantGroup[HNil](HNil)
  implicit val HNilMonoid: Monoid[HNil] = HNilGroup
  class HListMonoid[H, T <: HList](implicit hmon: Monoid[H], tmon: Monoid[T]) extends Monoid[::[H, T]] {
    def zero = hmon.zero :: tmon.zero
    def plus(a: ::[H, T], b: ::[H, T]) = 
      hmon.plus(a.head, b.head) :: tmon.plus(a.tail, b.tail)
  }
  implicit def hListMonoid[H, T <: HList](implicit hmon: Monoid[H], tmon: Monoid[T]) = new HListMonoid[H, T]

这让我可以写:

val result = Monoid.sum(
  1 :: 1 :: HNil,
  4 :: 3 :: HNil,
  2 :: 6 :: HNil)
// result is 7 :: 10 :: HNil

现在我可以对 HList 实例求和,缺少的部分似乎是定义可以对 ('name -&gt;&gt; 1) 形式的字段求和的 monoid 实例,我的 IDE 告诉我它具有以下类型:Int with record.KeyTag[Symbol with tag.Tagged[Constant(name).type] { .. }, Int] { .. }。在这一点上我被卡住了,因为我只是不知道该怎么做。

【问题讨论】:

    标签: scala record typeclass shapeless


    【解决方案1】:

    您非常接近 - 您只需在每个归纳步骤添加 FieldType[K, H] 而不是 H 并使用 field[K] 适当地键入您从 Monoid[H] 获得的值:

    import com.twitter.algebird._
    import shapeless._, labelled._, record._, syntax.singleton._
    
    implicit val hnilGroup: Group[HNil] = new ConstantGroup[HNil](HNil)
    implicit val hnilMonoid: Monoid[HNil] = hnilGroup
    implicit def hconsMonoid[K, H, T <: HList](implicit
      hm: Monoid[H],
      tm: Monoid[T]
    ): Monoid[FieldType[K, H] :: T] =
      Monoid.from(field[K](hm.zero) :: tm.zero) {
        case (hx :: tx, hy :: ty) => field[K](hm.plus(hx, hy)) :: tm.plus(tx, ty)
      }
    

    或者您可以使用 Shapeless 的 TypeClass 机器,它还为您提供案例类等的实例:

    import com.twitter.algebird._
    import shapeless._, ops.hlist._, ops.record._, record._, syntax.singleton._
    
    object MonoidHelper extends ProductTypeClassCompanion[Monoid] {
      object typeClass extends ProductTypeClass[Monoid] {
        def emptyProduct: Monoid[HNil] = Monoid.from[HNil](HNil)((_, _) => HNil)
        def product[H, T <: HList](hm: Monoid[H], tm: Monoid[T]): Monoid[H :: T] =
          Monoid.from(hm.zero :: tm.zero) {
            case (hx :: tx, hy :: ty) => hm.plus(hx, hy) :: tm.plus(tx, ty)
          }
    
        def project[F, G](m: => Monoid[G], to: F => G, from: G => F): Monoid[F] =
          Monoid.from(from(m.zero))((x, y) => from(m.plus(to(x), to(y))))
      }
    
      implicit def deriveRecordInstance[
        R <: HList,
        K <: HList,
        H,
        T <: HList
      ](implicit
        vs: Values.Aux[R, H :: T],        
        vm: Lazy[Monoid[H :: T]],
        ks: Keys.Aux[R, K],
        zk: ZipWithKeys.Aux[K, H :: T, R]
      ): Monoid[R] = typeClass.project(vm.value, vs(_), zk(_: H :: T))
    }
    
    import MonoidHelper._
    

    我在这里提供了一个derivedRecordInstance 方法,它可以在记录上进行这项工作,但我有点惊讶它是必要的——你可能会在未来版本的 Shapeless 中免费获得记录实例。

    【讨论】:

    • 感谢您的帮助,第一个解决方案对我有用。我相信第二个例子也有效,但我必须多研究一下才能理解它。我一直想尝试学习如何使用 Shapeless 的自动类型类派生,但目前该代码对我来说非常不透明。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-08
    • 1970-01-01
    • 2022-08-19
    • 2016-12-27
    • 2014-07-25
    • 2011-11-03
    相关资源
    最近更新 更多