The question was not about changing the data, but when you need to do this, you will quickly find that the Scala library has no tools to simplify (when the data is immutable). If you have not experienced this yet, try writing a function that replaces or changes the value
Cash
stored in Person
using the types defined in the question.
As described in Tony Morris's Asymmetric Lenses in Scala , lenses are a suitable solution to this problem.
Here is an example of how we can access and update a personโs Cash
value
using the Lens
and PLens
(partial lens) implementations from the scalaz-seven branch of Scalaz.
First, some pattern: define an instance of the lens for each field of the case classes. A @ -@ B
means the same as Lens[A, B]
.
val pants: Person @ -@ Option[Pants] = lensG(_.pants, p => ps => p.copy(pants = ps)) val pocket: Pants @ -@ Option[Pocket] = lensG(_.pocket, ps => p => ps.copy(pocket = p)) val cash: Pocket @ -@ Option[Cash] = lensG(_.cash, p => c => p.copy(cash = c)) val value: Cash @ -@ String = lensG(_.value, c => v => c.copy(value = v))
We cannot compose all these lenses because most fields are wrapped in Option
types.
Partial rescue lenses: they allow us to retrieve and update parts of a structure that may not exist, such as Some
for Option
or head
for List
.
We can use the somePLens
function from Scalaz 7 to create a partial lens that scans each optional field. However, in order to compose a partial lens with one of our conventional lenses, we need to access the equivalent instance of a partial lens for a conventional lens using the partial
method that exists on each Lens
.
// @-? is an infix type alias for PLens val someCash: Pocket @-? Cash = cash.partial andThen somePLens scala> someCash.get(Pocket(Some(Cash("zilch")))) res1: Option[Cash] = Some(Cash(zilch))
In the same way, we can create our partial lens that looks at the cash held by Person
, making up all instances of our partial
lenses and somePLens
sandwich instances. Here I used the <=<
operator, an alias for andThen
(which is equivalent to compose
when switching operands).
val someCashValue: Person @-? String = pants.partial <=< somePLens <=< pocket.partial <=< somePLens <=< cash.partial <=< somePLens <=< value.partial
Create an instance of Person
to play with:
val ben = Person(Some(Pants(Some(Pocket(Some(Cash("zilch")))))))
Using a partial lens to access the cash value I have:
scala> someCashValue.get(ben) res2: Option[String] = Some(zilch)
Using a partial lens to change the value:
scala> someCashValue.mod(_ + ", zero, nada", ben) res3: Person = Person(Some(Pants(Some(Pocket(Some(Cash(zilch, zero, nada)))))))
Now, if I have no pants (!), We can see how an attempt to change the value of my money will have no effect:
scala> val ben = Person(None) ben: Person = Person(None) scala> someCashValue.mod(_ + ", zero, nada", ben) res4: Person = Person(None)