为无形记录定义类型类
Define a Typeclass for Shapeless Records
我正在尝试学习 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 ->> 1)
的字段求和,我的 IDE 告诉我它具有以下类型:Int with record.KeyTag[Symbol with tag.Tagged[Constant(name).type] { .. }, Int] { .. }
。在这一点上我被卡住了,因为我不知道该怎么做。
你非常接近——你只需要在每个归纳步骤中添加 FieldType[K, H]
而不是 H
并使用 field[K]
键入你从 [=15= 获得的值]适当地:
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,我想定义一个将无形记录实例相加的幺半群。请注意,我使用的是 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 ->> 1)
的字段求和,我的 IDE 告诉我它具有以下类型:Int with record.KeyTag[Symbol with tag.Tagged[Constant(name).type] { .. }, Int] { .. }
。在这一点上我被卡住了,因为我不知道该怎么做。
你非常接近——你只需要在每个归纳步骤中添加 FieldType[K, H]
而不是 H
并使用 field[K]
键入你从 [=15= 获得的值]适当地:
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
方法,使它可以在记录上工作,但我有点惊讶它是必要的——你可能会在未来的版本中免费获得记录实例无形的。