I have been reading this article and in one of their section it is stated:
Lenses compose backwards. Can't we make
(.)
behave like functions?You're right, we could. We don't for various reasons, but the intuition is right. Lenses should combine just like functions. One thing that's important about that is id can either pre- or post- compose with any lens without affecting it.
What does that mean by Lenses compose backwards ?
Also, what does this mean: Can't we make (.)
behave like functions ?
(.)
is a function and by using it with Lens does it make (.)
to behave like something else ?
The Lens
type:
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
For our illustrative purposes, we can stick to the less general simple lens type, Lens'
. The right side then becomes:
forall f. Functor f => (a -> f a) -> s -> f s
Intuitively, (a -> f a)
is an operation on a part of a structure of type s
, which is promoted to an operation on the whole structure, (s -> f s)
. (The functor type constructor f
is part of the trickery which allows lenses to generalize getters, setters and lots of other things. We do not need to worry about it for now.) In other words:
(Note how, in the descriptions I just made, "part" and "whole" appear in different orders.)
Now, a lens is a function, and functions can be composed. As we know, (.)
has type:
(.) :: (y -> z) -> (x -> y) -> (x -> z)
Let us make the involved types simple lenses (For the sake of clarity, I will drop the constraint and the forall
). x
becomes a -> f a
, y
becomes s -> f s
and z
becomes t -> f t
. The specialized type of (.)
would then be:
((s -> f s) -> t -> f t) -> ((a -> f a) -> s -> f s) -> ((a -> f a) -> t -> f t)
The lens we get as result has type (a -> f a) -> (t -> f t)
. So, a composed lens firstLens . secondLens
takes an operation on the part focused by secondLens
and makes it an operation on the whole structure firstLens
aims at. That just happens to match the order in which OO-style field references are composed, which is opposite to the order in which vanilla Haskell record accessors are composed.
You could think of the Getter
part of a lens as a function, which you can extract using view
. For example, the lens way of writing the fst
function is:
view _1 :: (a,b) -> a
Now observe:
view _1 . view _2 :: (c, (a,b)) -> a -- First take the second pair element, then the first
view (_1 . _2) :: ((b,a) ,c) -> a -- This is "backwards" (exactly the opposite order of the above)
For lenses, (.)
doesn't behave like it would for functions. For functions, f . g
means "first apply g, then f", but for lenses, it means first use the lens f, then use the lens g
. Actually, the (.)
function is the same for both types, but lens' types make it seem like it's backwards.
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