12

同じタイプのインスタンス化されたケースクラスが2つあります。

case class Foo(x : Option[String], y : Option[String], z : Option[String])

インスタンス化されたクラスAおよびBを呼び出しましょう。

val a = Foo(x=Some("foo"), y=Some("bar"), z=Some("baz"))
val b = Foo(x=None, y=Some("etch"), z=None)

一般的な方法で、1回の操作でケースクラスAをBで更新できるかどうか疑問に思っています。

val c = b *oper* a // produces Foo(x=Some("foo"), y=Some("etch"), z=Some("baz")) 

Noneとして設定されているパラメータは無視されます。理想的には、操作は一般的なものである必要があります。そうすれば、あらゆるタイプのケースクラスに作用できます。

最初にクラスをタプル/リストに変換し、操作の完了後にクラスに戻すことで、Scalazでこれを行うことができるかもしれないという直感があります-おそらくApplicativeBuilderを使用しますか?私はこれについて正しい方法で考えていますか?何か案は?

4

2 に答える 2

24

かなり単純なScalazソリューション(あまり一般的ではありません)

セミグループインスタンスを使用して、多くの詳細をまとめることができます。

import scalaz._, Scalaz._

case class Foo(a: Option[String], b: Option[String], c: Option[String])

implicit object fooSemigroup extends Semigroup[Foo] {
  def fromFoo(f: Foo) = (f.a.fst, f.b.fst, f.c.fst)
  def toFoo(t: (FirstOption[String], FirstOption[String], FirstOption[String])) =
    Foo(t._1.value, t._2.value, t._3.value)
  def append(x: Foo, y: => Foo) = toFoo(fromFoo(x) |+| fromFoo(y))
}

それは私たちに与えます:

scala> val a = Foo(Some("foo"), Some("bar"), Some("baz"))
a: Foo = Foo(Some(foo),Some(bar),Some(baz))

scala> val b = Foo(None, Some("etch"), None)
b: Foo = Foo(None,Some(etch),None)

scala> b |+| a
res11: Foo = Foo(Some(foo),Some(etch),Some(baz))

あまり一般的ではありませんが、これはあなたが望むものだと思います。


Scalaz+シェイプレスソリューション

すべてのケースクラスで機能するものが必要な場合(メンバーに適切な型クラスインスタンスが与えられた場合)、ShapelessとScalazの次の組み合わせを使用できます。私はmissingfactorの答えとMilesSabinによるこの例を利用していることに注意してください。最初にいくつかのモノイドインスタンスの場合:

import scalaz._, Scalaz._
import shapeless._, HList._

implicit object hnilMonoid extends Monoid[HNil] {
  val zero = HNil
  def append(a: HNil, b: => HNil) = HNil
}

implicit def hlistMonoid[H, T <: HList](
  implicit mh: Monoid[H],
  mt: Monoid[T]
): Monoid[H :: T] = new Monoid[H :: T] {
  val zero = mh.zero :: mt.zero
  def append(a: H :: T, b: => H :: T) =
    (a.head |+| b.head) :: (a.tail |+| b.tail)
}

implicit def caseClassMonoid[C, L <: HList](
  implicit iso: Iso[C, L],
  ml: Monoid[L]
) = new Monoid[C] {
  val zero = iso.from(ml.zero)
  def append(a: C, b: => C) = iso.from(iso.to(a) |+| iso.to(b))
}

次に、簡単にするために、上記のようにラッパーOptionを使用する代わりに、スコープ内に「最初の」モノイドインスタンスを配置します。FirstOption

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

さて、私たちのケースクラスについて:

case class Foo(a: Option[String], b: Option[String], c: Option[String])

そして、Isoそれを前後に変換するインスタンスHList

implicit def fooIso = Iso.hlist(Foo.apply _, Foo.unapply _)

これで完了です。

scala> val a = Foo(Some("foo"), Some("bar"), Some("baz"))
a: Foo = Foo(Some(foo),Some(bar),Some(baz))

scala> val b = Foo(None, Some("etch"), None)
b: Foo = Foo(None,Some(etch),None)

scala> b |+| a
res0: Foo = Foo(Some(foo),Some(etch),Some(baz))

ここでもモノイドの代わりに半群を使用して数行を節約することもできますが、shapeless/examplesコードからできるだけ多くのコピーと貼り付けを回避しようとしていたので、それは演習として残しておきます。


パフォーマンス

パフォーマンスに関するコメントに対処するために、後者のソリューションとorElse(Scala 2.9.2、IcedTea7 2.2.1)を使用した標準ライブラリソリューションの完全に非科学的なベンチマークを次に示します。

def add(x: Foo, y: Foo) = Foo(x.a orElse y.a, x.b orElse y.b, x.c orElse y.c)

def ros = if (util.Random.nextBoolean)
  Some(util.Random.nextString(util.Random.nextInt(10))) else None

val foos = Seq.fill(500000)(Foo(ros, ros, ros))

def time(block: => Unit) = {
  val start = System.currentTimeMillis
  (block, System.currentTimeMillis - start)
}

そして、それぞれを数十回実行した後:

scala> Iterator.fill(10)(time(foos.reduce(add(_, _)))._2).sum / 10
res4: Long = 49

scala> Iterator.fill(10)(time(foos.reduce(_ |+| _))._2).sum / 10
res5: Long = 265

やや意外なことに、Shapeless-lessScalazソリューションは少し遅くなります。

scala> Iterator.fill(10)(time(foos.reduce(_.|+|(_)(fooSemigroup)))._2).sum / 10
res6: Long = 311

しかし、私が言ったように、これはベンチマークへの非常に簡単なアプローチであり、独自に実行する必要があります(Caliperはこのための優れたライブラリです)。

いずれにせよ、はい、あなたは抽象化にお金を払っていますが、それほど多くはありません、そしてそれはしばしばそれの価値があるでしょう。

于 2012-07-11T18:28:49.303 に答える
13

このスレッドで説明されているように、これは、完全にタイプセーフな方法で、シェイプレスの問題を解決する方法です。

scala> import shapeless._
import shapeless._

scala> import HList._
import HList._

scala> case class Foo(a: Option[Int], b: Option[Int])
defined class Foo

scala> val a = Foo(Some(3), None)
a: Foo = Foo(Some(3),None)

scala> val b = Foo(Some(22), Some(1))
b: Foo = Foo(Some(22),Some(1))

scala> implicit val fooIso = HListIso(Foo.apply _, Foo.unapply _)
fooIso: shapeless.HListIso[Foo,shapeless.::[Option[Int],shapeless.::[Option[Int],shapeless.HNil]]] = shapeless.HListIso@11c5b77

scala> type O2[+A] = (Option[A], Option[A])
defined type alias O2

scala> object mapper extends (O2 ~> Option) {
     |   def apply[A](x: O2[A]): Option[A] = x._1.orElse(x._2)
     | }
defined module mapper

scala> fooIso.fromHList(fooIso.toHList(a).zip(fooIso.toHList(b)).map(mapper))
res13: Foo = Foo(Some(3),Some(1))
于 2012-07-12T06:26:13.307 に答える