I have two instantiated case classes of the same type.
case class Foo(x : Option[String], y : Option[String], z : Option[String])
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)
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"))
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.
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?
As discusssed in this thread, this is how you can solve the problem with shapeless, in a completely type-safe manner.
Fairly straightforward Scalaz solution (not very general)
You can use a semigroup instance to wrap up a lot of the details:
Which gives us:
Which I think is what you want, although it's not very general.
Scalaz + Shapeless solution
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:
Next for the sake of simplicitly I'm just going to put the "First" monoid instance for
Option
in scope, instead of using theFirstOption
wrapper as I did above.Now for our case class:
And the
Iso
instance to convert it to anHList
and back:And we're done:
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.Performance
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):And then after running each a couple of dozen times:
Somewhat surprisingly, the Shapeless-less Scalaz solution is a little slower:
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.