更新 Scala 案例类的操作 [英] Update operations on a Scala Case Class

查看:31
本文介绍了更新 Scala 案例类的操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个相同类型的实例化案例类.

I have two instantiated case classes of the same type.

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

让我们调用实例化的类 A 和 B.

Lets call the instantiated classes A and B.

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

我想知道是否有可能以通用方式在单个操作中用 B 更新案例类 A.

I'm wondering if its possible to update case class A with B in a single operation in a generic way.

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

忽略设置为 None 的参数.理想情况下,操作也应该是通用的,以便它可以作用于任何类型的案例类.

with parameters that are set as None ignored. Ideally the operation should also be generic so it can act on any type of case class.

我有一些直觉,可以通过首先将类转换为元组/列表并在操作完成后转换回类来使用 Scalaz 执行此操作 - 也许使用 ApplicativeBuilder?我是否以正确的方式思考这个问题?有什么想法吗?

I have some intuition that it might be possible to do this with Scalaz by converting the class into a tuple/list first and converting back to a class after the operation is complete - perhaps using the ApplicativeBuilder? Am I thinking about this in the right way? Any ideas?

推荐答案

相当简单的 Scalaz 解决方案(不是很通用)

你可以使用一个半群实例来包装很多细节:

Fairly straightforward Scalaz solution (not very general)

You can use a semigroup instance to wrap up a lot of the details:

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

我认为这就是你想要的,虽然不是很普遍.

Which I think is what you want, although it's not very general.

如果你想要一些适用于所有 case 类的东西(为成员提供适当的类型类实例),你可以使用以下 无形 和 Scalaz.请注意,我正在利用 missingfactor 的答案这个例子 by Miles Sabin.首先是一些幺半群实例:

If you want something that works for all case classes (given the appropriate type class instances for members), you can use the following combination of Shapeless and Scalaz. Note that I'm drawing on missingfactor's answer and this example by Miles Sabin. First for some monoid instances:

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 包装器

Next for the sake of simplicitly I'm just going to put the "First" monoid instance for Option in scope, instead of using the FirstOption wrapper as I did above.

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 并返回:

And the Iso instance to convert it to an HList and back:

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 代码中尽可能多地复制和粘贴,所以我将其留作练习.

You could use semigroups instead of monoids here as well and save a few lines, but I was trying to get away with as much copying and pasting from the shapeless/examples code as possible, so I'll leave that as an exercise.

为了解决您对性能的评论,以下是后一种解决方案与使用 orElse(Scala 2.9.2、IcedTea7 2.2.1)的标准库解决方案的完全不科学的基准:

To address your comment about performance, here's a completely unscientific benchmark of the latter solution versus a standard library solution using 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)
}

然后在每个运行几十次之后:

And then after running each a couple of dozen times:

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-less Scalaz 解决方案的速度有点慢:

Somewhat surprisingly, the Shapeless-less Scalaz solution is a little slower:

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

但正如我所说,这是一种非常现成的基准测试方法,您应该运行自己的 (Caliper 是一个很好的库.

But as I said, this is an extremely off-the-cuff approach to benchmarking, and you should run your own (Caliper is a great library for this).

无论如何,是的,您为抽象付出了代价,但没有那么多,而且通常很值得.

In any case, yes, you're paying for the abstraction, but not that much, and it's often likely to be worth it.

这篇关于更新 Scala 案例类的操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆