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.