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.
statusCodemust 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.) - Reid Barton