I wanted to try lenses and the Monocle library seemed (from my noobish perspective) good with all those fancy boilerplate-less @Lenses
. Unfortunately I found out there are little to non learning materials for beginners (I know basics of FP in vanilla Scala, no Scalaz). Official tutorial lacks easy examples (and/or their results) and mixes in quite complex Scalaz library. One would assume that such trivial task like accessing a Map would be covered on a first page.
I have following snippet:
@Lenses case class House(presentsDelivered: Int)
type Houses = Map[(Int, Int), House]
@Lenses case class Town(houses: Houses)
@Lenses case class Santa(x: Int, y: Int)
@Lenses case class World(santa: Santa, town: Town)
I saw at
and index
, but no simple examples (just some weird [magic for me] answer with applyOptional
which required boilerplate). I want to update the map - houses
in Town
. I was trying something in this spirit:
(World.town ^|-> Town.houses ^|-> index((x, y)) ^|-> House.presentsDelivered)
.modify { _ + 1 }(world)
Which is syntactically wrong, but I think it's apparent what I wanted to do (modify presentsDelivered
of House
at specified x, y
coordinates). So my question is, how to modify the index
part to access the map?
Any help, clue or noob-friendly learning materials tips are welcome.
You're literally one character (and maybe an import) away from the solution:
import monocle.function.all.index
import monocle.std.map._
(
World.town ^|->
Town.houses ^|-?
index((0, 0)) ^|->
House.presentsDelivered
).modify(_ + 1)
Note that I've replaced the ^|->
immediately preceding the index with ^|-?
. This is necessary because index((x, y))
is fundamentally different from World.town
and the other macro-generated lenses for case class members. Those can't not point to a value, while index
can fail if there's no value at the given index in the map. In terms of Monocle's types, index((x, y))
is an Optional[Houses, House]
, while World.town
is a Lens[World, Town]
.
Optionals are weaker in a sense than lenses, and once you've composed a lens with an optional, you're going to continue to have optionals even if you compose more lenses. So the following is a lens:
World.town ^|-> Town.houses
But this is an optional:
World.town ^|-> Town.houses ^|-? index((0, 0)) ^|-> House.presentsDelivered
Monocle consistently uses x ^|-> y
to compose different types of x
(lenses, optionals, traversals, etc.) with lenses, and x ^|-? y
to compose different x
s with optionals. I personally find the operators a little confusing and prefer composeLens
, composeOptional
, etc., but tastes vary, and if you want to memorize the operators you can at least be confident that they're used consistently—you just need to know which one you need for a given type.
The other potential issue with your code is that you can't just write this:
import monocle.function.all.index
val houses: monocle.Optional[Houses, House] = index((0, 0))
This won't compile on its own because index
requires an instance of the Index
type class for the type that it's indexing into (in this case Map[(Int, Int), House]
. Monocle provides a generic instance for maps that will work, but you have to import it:
import monocle.std.map._
I'm afraid I don't have any terribly good suggestions for learning materials, but you can always ask questions here, and the Monocle Gitter channel is fairly active.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With