The right reading of r ^. responseStatus . statusCode
is r ^. (responseStatus . statusCode)
. This is only natural, since function composition returns a function when applied to two arguments, thus (r ^. responseStatus) . statusCode
must return a function, as opposed to any value that could be printed out.
This still leaves open the question why lenses compose in the "wrong" order. Since the implementation of lenses is a bit magical, let's look at a simpler example.
first
is a function that maps over the first element of a pair:
first :: (a -> b) -> (a, c) -> (b, c)
first f (a, b) = (f a, b)
What does map . first
do? first
takes a function acting on the first element, and returns a function acting on a pair, which is more apparent if we parenthesize the type this way:
first :: (a -> b) -> ((a, c) -> (b, c))
Also, recall the type of map
:
map :: (a -> b) -> ([a] -> [b])
map
takes a function acting on an element and returns a function acting on a list. Now, f . g
works by first applying g
and then feeding the result to f
. So map . first
takes a function acting on some element type, converts it to a function acting on pairs, then converts it to a function acting on lists of pairs.
(map . first) :: (a -> b) -> [(a, c)] -> [(b, c)]
first
and map
both turn functions acting on a part of a structure to functions acting on the whole structure. In map . first
, what is the whole structure for first
becomes the focus for map
.
(map . first) (+10) [(0, 2), (3, 4)] == [(10, 2), (13, 4)]
Now take a look at the type of lenses:
type Lens = forall f. Functor f => (a -> f b) -> (s -> f t)
Try to ignore the Functor
bits for now. If we squint slightly, this resembles the types for map
and first
. And it happens so that lenses also convert functions acting on parts of structures into function acting on whole structures. In the signature above s
denotes the whole structure and a
denotes a part of it. Since our input function can change the type of a
to b
(as indicated by a -> f b
), we also need the t
parameter, which roughly means "the type of s
after we changed a
to b
inside it".
statusCode
is a lens that converts a function acting on an Int
to a function acting on a Status
:
statusCode :: Functor f => (Int -> f Int) -> (Status -> f Status)
responseStatus
converts a function acting on a Status
to a function acting on a Response
:
responseStatus :: Functor f => (Status -> f Status) -> (Response -> f Response)
The type of responseStatus . statusCode
follows the same pattern as we've seen with map . first
:
responseStatus . statusCode :: Functor f => (Int -> f Int) -> (Response -> f Response)
It remains to be seen how exactly ^.
works. It's intimately tied to the core mechanic and magic of lenses; I will not reiterate it here, since there are quite a few writings about it. For an introduction I recommend looking at this one and this one, and you could also watch this excellent video.
statusCode
must be a lens, not the record field selector. I guess they must have hid the field selector and exported a lens of the same name; pretty confusing if you ask me. (Or wrote a custom Show instance.) – Fillip