13

このケースクラスの質問に似ていますが、ひねりがあります:

プロパティとしていくつかの深くネストされたケース クラスを持つケース クラスがあります。簡単な例として、

case class Foo(fooPropA:Option[String], fooPropB:Option[Int])
case class Bar(barPropA:String, barPropB:Int)
case class FooBar(name:Option[String], foo:Foo, optionFoo: Option[Foo], bar:Option[Bar])

2 つの FooBar ケース クラスをマージして、入力用に存在する値を取得し、それらを既存のインスタンスに適用して、更新されたバージョンを生成したいと思います。

val fb1 = FooBar(Some("one"), Foo(Some("propA"), None), Some(Foo(Some("propA"), Some(3))), Some(Bar("propA", 4)))
val fb2 = FooBar(None, Foo(Some("updated"), Some(2)), Some(Foo(Some("baz"), None)), None)
val merged = fb1.merge(fb2)
//merged = FooBar(Some("one"), Foo(Some("updated"), Some(2)), Some(Foo(Some("baz"), Some(3))), Some(Bar("propA", 4)))

レンズを使用して、深くネストされたプロパティの更新を作成できることはわかっています。ただし、これには多くの定型コードが必要になると思います。すべてのプロパティにレンズが必要であり、親クラスに別の合成レンズが必要です。shapelessでより簡潔なレンズ作成アプローチを使用したとしても、これは維持するのが大変に思えます。

注意が必要なのは optionFoo 要素です。このシナリオでは、両方の要素が Some(value) と共に存在します。ただし、fb1 を fb2 の新しい値で上書きするだけでなく、内部オプション プロパティをマージしたいと考えています。

これらの 2 つの値を最小限のコードでマージする良い方法があるかどうか疑問に思っています。私の直感unapplyでは、ケース クラスでメソッドを使用してタプルを返し、繰り返し処理してタプルを新しいタプルに結合し、そのタプルをケース クラスに適用し直すように指示されています。

これを行うためのより効率的な方法はありますか?

4

3 に答える 3

10

この問題に取り組む明確な方法の 1 つは、マージ操作を、適切なモノイド インスタンスのセットが与えられた場合の加算のようなものと考えることです。非常によく似た問題の解決策として、私の回答をここで見ることができますが、タイプレベルチームの努力のおかげで、解決策はさらに簡単になりました。まず、ケース クラスの場合:

case class Foo(fooPropA: Option[String], fooPropB: Option[Int])
case class Bar(barPropA: String, barPropB: Int)
case class FooBar(name: Option[String], foo: Foo, bar: Option[Bar])

次に、ボイラープレート (Shapeless の次の 2.0 リリースでは必要ありません):

import shapeless._

implicit def fooIso = Iso.hlist(Foo.apply _, Foo.unapply _)
implicit def barIso = Iso.hlist(Bar.apply _, Bar.unapply _)
implicit def fooBarIso = Iso.hlist(FooBar.apply _, FooBar.unapply _)

Optionわかりやすくするために、タグを使用する代わりに「2 番目の」モノイド インスタンスをスコープに入れます。

import scalaz._, Scalaz._
import shapeless.contrib.scalaz._

implicit def optionSecondMonoid[A] = new Monoid[Option[A]] {
  val zero = None
  def append(a: Option[A], b: => Option[A]) = b orElse a
}

これで完了です。

scala> val fb1 = FooBar(Some("1"), Foo(Some("A"), None), Some(Bar("A", 4)))
fb1: FooBar = FooBar(Some(one),Foo(Some(propA),None),Some(Bar(propA,4)))

scala> val fb2 = FooBar(None, Foo(Some("updated"), Some(2)), None)
fb2: FooBar = FooBar(None,Foo(Some(updated),Some(2)),None)

scala> fb1 |+| fb2
res0: FooBar = FooBar(Some(1),Foo(Some(updated),Some(2)),Some(Bar(A,4)))

追加の議論については、私の以前の回答を参照してください。

于 2013-08-02T21:49:45.067 に答える
5

以前の回答では Shapeless 1.2.4、Scalaz、および shapeless-contrib を使用していましたが、Shapeless 1.2.4 と shapeless-contrib はこの時点 (2 年以上後) ではかなり古くなっているため、Shapeless 2.2.5 とを使用した更新された回答を次に示します。 0.3.0。次のようなビルド構成を想定します。

scalaVersion := "2.11.7"

libraryDependencies ++= Seq(
  "com.chuusai" %% "shapeless" % "2.2.5",
  "org.spire-math" %% "cats" % "0.3.0"
)

Shapeless には、ProductTypeClassここで使用できる型クラスが含まれるようになりました。最終的には、Miles Sabin の子猫プロジェクト (または類似のもの) が猫の型クラスにこの種のものを提供する可能性があります (shapeless-contrib が Scalaz で果たした役割と同様) ProductTypeClass

import algebra.Monoid, cats.std.all._, shapeless._

object caseClassMonoids extends ProductTypeClassCompanion[Monoid] {
  object typeClass extends ProductTypeClass[Monoid] {
    def product[H, T <: HList](ch: Monoid[H], ct: Monoid[T]): Monoid[H :: T] =
      new Monoid[H :: T] {
        def empty: H :: T = ch.empty :: ct.empty
        def combine(x: H :: T, y: H :: T): H :: T =
         ch.combine(x.head, y.head) :: ct.combine(x.tail, y.tail)
      }

    val emptyProduct: Monoid[HNil] = new Monoid[HNil] {
      def empty: HNil = HNil
      def combine(x: HNil, y: HNil): HNil = HNil
    }

    def project[F, G](inst: => Monoid[G], to: F => G, from: G => F): Monoid[F] =
      new Monoid[F] {
        def empty: F = from(inst.empty)
        def combine(x: F, y: F): F = from(inst.combine(to(x), to(y)))
      }
  }
}

その後:

import cats.syntax.semigroup._
import caseClassMonoids._

case class Foo(fooPropA: Option[String], fooPropB: Option[Int])
case class Bar(barPropA: String, barPropB: Int)
case class FooBar(name: Option[String], foo: Foo, bar: Option[Bar])

そして最後に:

scala> val fb1 = FooBar(Some("1"), Foo(Some("A"), None), Some(Bar("A", 4)))
fb1: FooBar = FooBar(Some(1),Foo(Some(A),None),Some(Bar(A,4)))

scala> val fb2 = FooBar(None, Foo(Some("updated"), Some(2)), None)
fb2: FooBar = FooBar(None,Foo(Some(updated),Some(2)),None)

scala> fb1 |+| fb2
res0: FooBar = FooBar(Some(1),Foo(Some(Aupdated),Some(2)),Some(Bar(A,4)))

Someこれは、質問が求めているものとは正確には異なりますが、私の他の回答のコメントでOPによって言及されていることに注意してください。置換動作が必要な場合Monoid[Option[A]]は、他の回答のように適切なものを定義できます。

于 2015-11-09T16:38:41.757 に答える
2

Kittens 1.0.0-M8を使用して、ボイラープレートをまったく使用せずSemigroupに (この例では十分だと思いましたMonoidが、単にインポートするだけです)を導出できるようになりました。

import cats.implicits._
import cats.derived._, semigroup._, legacy._

case class Foo(fooPropA: Option[String], fooPropB: Option[Int])
case class Bar(barPropA: String, barPropB: Int)
case class FooBar(name: Option[String], foo: Foo, bar: Option[Bar])

val fb1 = FooBar(Some("1"), Foo(Some("A"), None), Some(Bar("A", 4)))

val fb2 = FooBar(None, Foo(Some("updated"), Some(2)), None)
println(fb1 |+| fb2)

収量:

FooBar(Some(1),Foo(Some(Aupdated),Some(2)),Some(Bar(A,4)))
于 2017-07-16T12:20:44.593 に答える