scala - 更新 Scala 案例类的操作

标签 scala scalaz

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

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)

我想知道是否有可能以通用方式在单个操作中用 B 更新案例类 A。
val c = b *oper* a // produces Foo(x=Some("foo"), y=Some("etch"), z=Some("baz")) 

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

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

最佳答案

相当简单的 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's answerthis example迈尔斯·萨宾。首先是一些幺半群实例:
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-less Scalaz 解决方案有点慢:
scala> Iterator.fill(10)(time(foos.reduce(_.|+|(_)(fooSemigroup)))._2).sum / 10
res6: Long = 311

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

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

关于scala - 更新 Scala 案例类的操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11438277/

相关文章:

scala - 在 scala 中基于鸭子类型定义泛型类型?

scala - neo4j 3.0 嵌入式 - 无节点

java - Spark java.lang.NoClassDefFoundError : com/datastax/driver/core/ProtocolOptions$Compression 中的 Spark-cassandra-connector 错误

scala - 如何处理 `Reader` monad 和 `Try` ?

scala - 在 EitherT 上的左侧 flatMap

scala - 以字符串表示日期2018-01-01T17:11:11.111 + 06:00的格式

scala - 组成 scalaz io 效果对象

scala - 使用 Scalaz(或 Shapeless)为每个子类创建 Monoid

scala - 如何结合 Reader 和 State monad

scala - 三元运算符类似于 ? :