我想将类的更新实例合并到基本实例中,如果基本实例中的该字段为“空”,则选择基础实例上的更新实例的字段。以下示例合并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
。知道如何实现我的目标吗?
谢谢!
答案 0 :(得分:5)
对于像这样的特定任务,创建类型类通常比使用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)
}
答案 1 :(得分:2)
您可以使用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])
}
}
编辑:弄清楚如何使用Poly匹配元组中嵌入的None和Nil对象类型。添加它并简化代码。