Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lenses: Composing backwards and (.) in Lens context

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 ?

like image 335
Sibi Avatar asked Dec 25 '22 10:12

Sibi


2 Answers

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:

  • From the user point of view, a lens allows to, given a whole, focus on a part of it.
  • Implementation-wise, a lens is a function which takes a function of the part and results in a function of the whole.

(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.

like image 142
duplode Avatar answered Jan 09 '23 06:01

duplode


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.

like image 21
bennofs Avatar answered Jan 09 '23 07:01

bennofs