2016-06-23 6 views
2

クラスの更新インスタンスをベースインスタンスにマージし、そのインスタンスがベースで「空」の場合はベースインスタンス上の更新インスタンスのフィールドを選択したいインスタンス。私はこのような何かを試してみた同じケースクラスの2つのインスタンスをマージするためにシェイプレスを使用

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) 
    }) 

をしかし、それはない出力HListあたりが言うんジェネリック.to方法が、Reprが判明以下の例では、baseupdateをマージします。どのように私は私の目標を達成することができましたか?

ありがとうございました!

答えて

5

このような具体的なタスクの場合、mapPolyのようなものを入力するよりも、しばしば型クラスを作成する方が簡単です。

次のように我々はいくつかのTにアップデートを表現することができます

trait Update[T] { 
    def apply(base: T, update: T): T 
} 

は、今、私たちはいくつかのインスタンスを定義する必要があります。 ListOptionといくつかのインスタンスを更新して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したがって、OptionListのインスタンスを省略することができましたES、例えばUpdate[Vector[Int]]得ながら:あなたはPolymapは非常に一般的な方法でこの問題を解決するために使用することができます

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) 
    } 
+0

ニースの解決策、thx! 'genericUpdate'を別の特性に入れているのはなぜですか?なぜ更新でそれを持っていないのですか? –

+0

それはそうですが、 'Update'でうまくいくはずです。しかし、例えば、カスタムの 'Update((A、B)]'を作成したい場合、 'genericUpdate'は' trait'の中にある必要があります。そうしないと、あいまいなimplicitsを得ることになります。 –

+0

私はコンパイルエラーがあるので、 'Update [Foo]'暗黙的に見つけることができません。私は 'Update'特性とオブジェクトをファイルに持っています。パッケージオブジェクト内の '' UpdateOps [A] '。パッケージオブジェクトと 'Update'オブジェクトの内容の両方をインポートします。 'GenericUpdate'が見つからないようです。もう一つの注意点では、 'genericUpdate'で' G'とは何ですか? –

2

。私は解決策がかなりエレガントだと思う。

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のオブジェクトタイプをマッチングさせる方法を理解しました。それを追加し、コードを単純化しました。

関連する問題