Why is <$> left associative?
Asked Answered
G

1

9

fmap is also <$> because it is function application ($) in the functor category.

(+5)  $  (*10)  $  10      -- 105
(+5) <$> (*10) <$> [1,2,3] -- [15,25,35]

Then I thought, well in that case <*> is function application in the applicative functor category, and this should work:

[(+5), (+10)] <*>  [(*10), (*100)] <*> [1,2,3]  -- ERROR
-- however:
[(+5), (+10)] <*> ([(*10), (*100)] <*> [1,2,3]) -- [15,25,35,...]

So, <$> only works out because fmap for functions happens to be postcomposition, so (+5) <$> (*10) becomes (+5) . (*10) which is then applied to [1,2,3].

However left associativity for all the application operators (<<= included) seems to me like a poor design choice, especially after they recognized the similarity to $ which is already right-associative. Is there another reason for this?

Galumph answered 31/12, 2017 at 14:13 Comment(2)
Function application is inherently left-associative; a better question would be why is $ right-associative?Apiarist
Also, fmap is better thought of as function lifting rather than function application. Once fmap returns the lifted function, it is applied the same as any other function; no special application required.Apiarist
C
10

Really, the reason is probably just that it allows <$> and <*> to share one precedence level. We definitely want <*> to be left-associative so stuff like

Prelude> foldr <$> [(+),(*)] <*> [0,1] <*> [[1,2,3], [4,5,6]]
[6,15,7,16,0,0,6,120]

works, and this also makes <$> behave the correct way even though it doesn't have higher precedence. Actually chaining multiple <$> operators is indeed not very useful with left-associativity.

However, it would also not be very useful with right-associativity. As chepner commented, it's actually a bit funny that $ is right-associative. Sure, that allows writing compositions like

Prelude> sum $ map (+3) $ take 19 $ cycle [4..7]
160

but then, this could just as well be written as the arguably more elegant

Prelude> sum . map (+3) . take 19 . cycle $ [4..7]
160

(more elegant I say, because here the computation-chain is parsed as a single functional pipeline, rather than imperative-style “do this, then that, then...”). Thanks to the functor laws, this can be done just the same way with <$> and . as with $ and ..

The only reason why you might prefer the multiple-$ style is that it allows infix expressions in the pipeline, perhaps the most common example being lens updates (which are typically written with the flipped &, but the principle is the same):

Prelude Control.Lens> [4..7] & ix 1+~9 & ix 2*~8
[4,14,48,7]

This works because $ and & have very low precedence, pretty much lower than any infix operator. That's not the case for <$> so you can't do

Prelude Control.Lens> ix 1+~9 <$> [[4..8], [5..9]]

<interactive>:23:1: error:
    Precedence parsing error
        cannot mix ‘+~’ [infixr 4] and ‘<$>’ [infixl 4] in the same infix expression

In such a case, you need to use some parentheses anyway, and then you might as well do it with the also low-precedence composition operators from Control.Category:

Prelude Control.Lens Control.Category> (ix 1+~9 >>> ix 2*~8) <$> [[4..8], [5..9]]
[[4,14,48,7,8],[5,15,56,8,9]]

or with parens around each updater:

Prelude Control.Lens> (ix 1+~9) . (ix 2*~8) <$> [[4..8], [5..9]]
[[4,14,48,7,8],[5,15,56,8,9]]
Coppinger answered 31/12, 2017 at 15:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.