A lens is really just a function a -> b
that we
represent backwardsI’m exaggerating a bit here 😅 To see what I
really mean, see this post.
and with an extra Functor f
parameter lying
around:
type Lens' a b = Functor f => (b -> f b) -> (a -> f a)
What does this mean for function composition?
Normal function composition looks like this:
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f :: a -> b
g :: b -> c
. f :: (a -> c) g
We often have to read code that looks like this:
. f $ x g
This means “start with x
, then run f
on it,
and run g
after that.” This sentence reads opposite from
how the code reads!
What about for lenses? Here we have f'
and
g'
which behave similarly in some sense to f
and g
from before:
f' :: Functor f => (b -> f b) -> (a -> f a)
-- ≈ a -> b
g' :: Functor f => (c -> f c) -> (b -> f b)
-- ≈ b -> c
. g' :: Functor f => (c -> f c) -> (a -> f a)
f' -- ≈ a -> c
In the lens world, ^.
behaves kind of like a flipped
$
that turns lenses into getters, which lets us write code
like this:
^. f' . g' x
This means “start with x
, then get f'
from
it, then get g'
after that.” The sentence reads just like
the code!
This is pretty cool, because it means that lenses (which are “functional” getters) read almost exactly like property access (which are “imperative” getters). Same concise syntax, but with an elegant semantics.