Let's say I have a case class that represents personas, people on different social networks. Instances of that class are fully immutable, and are held in immutable collections, to be eventually modified by an Akka actor.
Now, I have a case class with many fields, and I receive a message that says I must update one of the fields, something like this:
case class Persona(serviceName : String,
serviceId : String,
sentMessages : Set[String])
// Somewhere deep in an actor
val newPersona = Persona(existingPersona.serviceName,
existingPersona.serviceId,
existingPersona.sentMessages + newMessage)
Notice I have to specify all fields, even though only one changes. Is there a way to clone existingPersona and replace only one field, without specifying all the fields that don't change? Can I write that as a trait and use it for all my case classes?
If Persona was 开发者_如何转开发a Map-like instance, it would be easy to do.
case class
comes with a copy
method that is dedicated exactly to this usage:
val newPersona = existingPersona.copy(sentMessages =
existingPersona.sentMessages + newMessage)
Since 2.8, Scala case classes have a copy
method that takes advantage of named/default params to work its magic:
val newPersona =
existingPersona.copy(sentMessages = existing.sentMessages + newMessage)
You can also create a method on Persona
to simplify usage:
case class Persona(
svcName : String,
svcId : String,
sentMsgs : Set[String]
) {
def plusMsg(msg: String) = this.copy(sentMsgs = this.sentMsgs + msg)
}
then
val newPersona = existingPersona plusMsg newMsg
existingPersona.copy(sentMessages = existingPersona.sentMessages + newMessage)
Consider using lens
in Shapeless
library:
import shapeless.lens
case class Persona(serviceName : String,
serviceId : String,
sentMessages : Set[String])
// define the lens
val messageLens = lens[Persona] >> 'sentMessages
val existingPersona = Persona("store", "apple", Set("iPhone"))
// When you need the new copy, by setting the value,
val newPersona1 = messageLens.set(existingPersona)(Set.empty)
// or by other operation based on current value.
val newPersona2 = messageLens.modify(existingPersona)(_ + "iPad")
// Results:
// newPersona1: Persona(store,apple,Set())
// newPersona2: Persona(store,apple,Set(iPhone, iPad))
Moreover, in case you have nested case classes, the getter
and setter
methods can be a bit tedious to compose. It will be a good chance to simplify by using lens library.
Please also refer to:
- Shapeless Github / Boilerplate-free lenses for arbitrary case classes
- Quicklens Github
- Lens in scala
I didn't want to include a big library to do complex lenses that let you set values deep in nested case classes. It turns out it is just a few lines of code in the scalaz library:
/** http://stackoverflow.com/a/5597750/329496 */
case class Lens[A, B](get: A => B, set: (A, B) => A) extends ((A) => B) with Immutable {
def apply(whole: A): B = get(whole)
def mod(a: A, f: B => B) = set(a, f(this (a)))
def compose[C](that: Lens[C, A]) = Lens[C, B](
c => this(that(c)),
(c, b) => that.mod(c, set(_, b))
)
def andThen[C](that: Lens[B, C]) = that compose this
}
You can then create lenses that set deeply nested values far easier than using the built in copy feature. Here is a link to a big set if complex lenses that that my library uses to set heavily nested values.
精彩评论