使用 shapeless 合并同一案例的两个实例 class
Use shapeless to merge two instances of the same case class
我想将 class 的更新实例合并到基础实例中,如果该字段在基础实例中是 "empty",则选择更新实例的字段而不是基础实例。下面的例子合并了 base
和 update
:
case class Foo(a: Option[Int], b: List[Int], c: Option[Int])
val base = Foo(None, Nil, Some(0))
val update = Foo(Some(3), List(4), None)
merge(base,update) == Foo(Some(3), List(4), Some(0))
我试过这样的事情:
val g = Generic[Foo]
val aHList = g.to(base)
val bHList = g.to(update)
aHList
.zipWithIndex
.map({
case (l: List, i: Int) => l ++ bHList(i)
case (o: Option, i: Int) => if (!bHList(i).nonEmpty) {
updateHList(i)
} else {
o
}
case (_, i: Int) => updateHList(i)
})
但事实证明,通用 .to
方法不会输出一个 HList
,而是一个 Repr
。知道如何实现我的目标吗?
谢谢!
对于像这样的特定任务,创建类型 class 通常比涉足 map
和 Poly
之类的东西更容易。
我们可以表示一些 T
的更新,如下所示:
trait Update[T] {
def apply(base: T, update: T): T
}
现在我们需要定义一些实例。如何更新 List
和 Option
以及一些实例以派生出 Update[Foo]
实例。
import shapeless._
object Update extends Update0 {
def apply[A](implicit update: Lazy[Update[A]]): Update[A] = update.value
implicit def optionUpdate[A]: Update[Option[A]] =
new Update[Option[A]] {
def apply(base: Option[A], update: Option[A]): Option[A] = update orElse base
}
implicit def listUpdate[A]: Update[List[A]] =
new Update[List[A]] {
def apply(base: List[A], update: List[A]): List[A] = base ++ update
}
implicit def hnilUpdate: Update[HNil] =
new Update[HNil] {
def apply(base: HNil, update: HNil): HNil = HNil
}
implicit def hconsUpdate[H, T <: HList](
implicit updateH: Update[H], updateT: Lazy[Update[T]]
): Update[H :: T] =
new Update[H :: T] {
def apply(base: H :: T, update: H :: T): H :: T =
updateH(base.head, update.head) :: updateT.value(base.tail, update.tail)
}
}
trait Update0 {
implicit def genericUpdate[A, G <: HList](
implicit gen: Generic.Aux[A, G], updateG: Lazy[Update[G]]
): Update[A] =
new Update[A] {
def apply(base: A, update: A): A =
gen.from(updateG.value(gen.to(base), gen.to(update)))
}
}
我们可以添加一些语法使其更容易一些:
implicit class UpdateOps[A](val base: A) extends AnyVal {
def update(change: A)(implicit update: Lazy[Update[A]]): A =
update.value(base, change)
}
现在我们可以做:
case class Foo(a: Option[Int], b: List[Int], c: Option[Int])
val base = Foo(None, Nil, Some(0))
val update = Foo(Some(3), List(4), None)
base update update // Foo(Some(3),List(4),Some(0))
我们可以为 cats.SemigroupK
或 scalaz.Plus
定义一个实例,这样我们就可以省略 Option
和 List
实例,同时获得例如 Update[Vector[Int]]
:
import cats.SemigroupK
import cats.implicits._
implicit def semigroupKUpdate[F[_], A](implicit F: SemigroupK[F]): Update[F[A]] =
new Update[F[A]] {
def apply(base: F[A], update: F[A]): F[A] = F.combineK(update, base)
}
您可以使用Poly
和map
以非常通用的方式解决这个问题。我认为解决方案非常优雅。
import shapeless._
import shapeless.ops.hlist._
import syntax.std.tuple._
trait LowPriority extends Poly1 {
implicit def default[T, U] = at[(T, U)]{ case (t, u) => t }
}
object ChooseNonEmpty extends LowPriority {
type OverNone[A] = (None.type, A)
type OverNil[A] = (Nil.type, A)
implicit def caseNone[T] = at[OverNone[T]] { case (t, u) => u }
implicit def caseList[T] = at[OverNil[T]] { case (t, u) => u }
}
object test {
def merge[C, HF <: Poly, Repr <: HList, ZRepr <: HList, MRepr <: HList](f:HF)(base:C, update:C)
(implicit
gen:Generic.Aux[C, Repr],
zipper:Zip.Aux[Repr :: Repr :: HNil, ZRepr],
mapper:Mapper.Aux[f.type, ZRepr, MRepr]): C = {
val basep = gen.to(base)
val updatep = gen.to(update)
val zipped = basep zip updatep
gen.from( (zipped map f).asInstanceOf[Repr])
}
}
编辑:弄清楚了如何将嵌入在元组中的 None 和 Nil 对象类型与多边形相匹配。添加它并简化了代码。
我想将 class 的更新实例合并到基础实例中,如果该字段在基础实例中是 "empty",则选择更新实例的字段而不是基础实例。下面的例子合并了 base
和 update
:
case class Foo(a: Option[Int], b: List[Int], c: Option[Int])
val base = Foo(None, Nil, Some(0))
val update = Foo(Some(3), List(4), None)
merge(base,update) == Foo(Some(3), List(4), Some(0))
我试过这样的事情:
val g = Generic[Foo]
val aHList = g.to(base)
val bHList = g.to(update)
aHList
.zipWithIndex
.map({
case (l: List, i: Int) => l ++ bHList(i)
case (o: Option, i: Int) => if (!bHList(i).nonEmpty) {
updateHList(i)
} else {
o
}
case (_, i: Int) => updateHList(i)
})
但事实证明,通用 .to
方法不会输出一个 HList
,而是一个 Repr
。知道如何实现我的目标吗?
谢谢!
对于像这样的特定任务,创建类型 class 通常比涉足 map
和 Poly
之类的东西更容易。
我们可以表示一些 T
的更新,如下所示:
trait Update[T] {
def apply(base: T, update: T): T
}
现在我们需要定义一些实例。如何更新 List
和 Option
以及一些实例以派生出 Update[Foo]
实例。
import shapeless._
object Update extends Update0 {
def apply[A](implicit update: Lazy[Update[A]]): Update[A] = update.value
implicit def optionUpdate[A]: Update[Option[A]] =
new Update[Option[A]] {
def apply(base: Option[A], update: Option[A]): Option[A] = update orElse base
}
implicit def listUpdate[A]: Update[List[A]] =
new Update[List[A]] {
def apply(base: List[A], update: List[A]): List[A] = base ++ update
}
implicit def hnilUpdate: Update[HNil] =
new Update[HNil] {
def apply(base: HNil, update: HNil): HNil = HNil
}
implicit def hconsUpdate[H, T <: HList](
implicit updateH: Update[H], updateT: Lazy[Update[T]]
): Update[H :: T] =
new Update[H :: T] {
def apply(base: H :: T, update: H :: T): H :: T =
updateH(base.head, update.head) :: updateT.value(base.tail, update.tail)
}
}
trait Update0 {
implicit def genericUpdate[A, G <: HList](
implicit gen: Generic.Aux[A, G], updateG: Lazy[Update[G]]
): Update[A] =
new Update[A] {
def apply(base: A, update: A): A =
gen.from(updateG.value(gen.to(base), gen.to(update)))
}
}
我们可以添加一些语法使其更容易一些:
implicit class UpdateOps[A](val base: A) extends AnyVal {
def update(change: A)(implicit update: Lazy[Update[A]]): A =
update.value(base, change)
}
现在我们可以做:
case class Foo(a: Option[Int], b: List[Int], c: Option[Int])
val base = Foo(None, Nil, Some(0))
val update = Foo(Some(3), List(4), None)
base update update // Foo(Some(3),List(4),Some(0))
我们可以为 cats.SemigroupK
或 scalaz.Plus
定义一个实例,这样我们就可以省略 Option
和 List
实例,同时获得例如 Update[Vector[Int]]
:
import cats.SemigroupK
import cats.implicits._
implicit def semigroupKUpdate[F[_], A](implicit F: SemigroupK[F]): Update[F[A]] =
new Update[F[A]] {
def apply(base: F[A], update: F[A]): F[A] = F.combineK(update, base)
}
您可以使用Poly
和map
以非常通用的方式解决这个问题。我认为解决方案非常优雅。
import shapeless._
import shapeless.ops.hlist._
import syntax.std.tuple._
trait LowPriority extends Poly1 {
implicit def default[T, U] = at[(T, U)]{ case (t, u) => t }
}
object ChooseNonEmpty extends LowPriority {
type OverNone[A] = (None.type, A)
type OverNil[A] = (Nil.type, A)
implicit def caseNone[T] = at[OverNone[T]] { case (t, u) => u }
implicit def caseList[T] = at[OverNil[T]] { case (t, u) => u }
}
object test {
def merge[C, HF <: Poly, Repr <: HList, ZRepr <: HList, MRepr <: HList](f:HF)(base:C, update:C)
(implicit
gen:Generic.Aux[C, Repr],
zipper:Zip.Aux[Repr :: Repr :: HNil, ZRepr],
mapper:Mapper.Aux[f.type, ZRepr, MRepr]): C = {
val basep = gen.to(base)
val updatep = gen.to(update)
val zipped = basep zip updatep
gen.from( (zipped map f).asInstanceOf[Repr])
}
}
编辑:弄清楚了如何将嵌入在元组中的 None 和 Nil 对象类型与多边形相匹配。添加它并简化了代码。