Actualizar operaciones en una clase de caso Scala

Tengo dos clases de casos instanciadas del mismo tipo.

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

Llamemos a las clases instanciadas A y B.

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

Me pregunto si es posible actualizar la clase de caso A con B en una sola operación de una manera genérica.

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

con parámetros que se establecen como Ninguno ignorado. Idealmente, la operación también debería ser genérica para que pueda actuar sobre cualquier tipo de clase de caso.

Tengo la intuición de que es posible hacer esto con Scalaz convirtiendo primero la clase en una tupla / lista y convirtiendo de nuevo a una clase una vez que se complete la operación, ¿quizás usando el ApplicativeBuilder? ¿Estoy pensando en esto de la manera correcta? ¿Algunas ideas?

Solución bastante sencilla de Scalaz (no muy general)

Puede usar una instancia de semigroup para completar muchos de los detalles:

 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) = (fafst, fbfst, fcfst) 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)) } 

Lo que nos da:

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

Lo cual creo que es lo que quieres, aunque no es muy general.


Solución Scalaz + Shapeless

Si desea algo que funcione para todas las clases de casos (dadas las instancias de clase de tipo apropiadas para los miembros), puede usar la siguiente combinación de Shapeless y Scalaz. Tenga en cuenta que estoy recurriendo a la respuesta del factor que falta y a este ejemplo de Miles Sabin. Primero para algunas instancias de monoid:

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

Luego, simplemente por simplificar, voy a poner la “Primera” instancia de FirstOption para Option en el scope, en lugar de usar el contenedor de FirstOption como lo hice anteriormente.

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

Ahora para nuestra clase de caso:

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

Y la instancia de Iso para convertirlo en HList y viceversa:

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

Y hemos terminado:

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

También podría usar semigrupos en lugar de monoides aquí y guardar algunas líneas, pero yo estaba tratando de salirse con tanto copiar y pegar del código shapeless/examples como fuera posible, así que lo dejo como un ejercicio.


Actuación

Para abordar su comentario sobre el rendimiento, aquí hay un punto de referencia completamente anticientífico de la última solución frente a una solución de biblioteca estándar con orElse (Scala 2.9.2, IcedTea7 2.2.1):

 def add(x: Foo, y: Foo) = Foo(xa orElse ya, xb orElse yb, xc orElse yc) 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) } 

Y luego, después de ejecutar cada uno un par de docenas de veces:

 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 

Sorprendentemente, la solución Scalaz sin Shapeless es un poco más lenta:

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

Pero como dije, este es un enfoque extremadamente improvisado para la evaluación comparativa, y debe ejecutar el suyo propio ( Caliper es una gran biblioteca para esto).

En cualquier caso, sí, estás pagando por la abstracción, pero no tanto, y con frecuencia es probable que valga la pena.

Como se discutió en este hilo , así es como se puede resolver el problema sin forma , de una manera completamente segura.

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