A curious associativity of the <$> operator
Published on
The <$> operator in Haskell is an infix synonym
for fmap and has the type
(<$>) :: Functor f => (a -> b) -> f a -> f bFor example:
> negate <$> Just 5
Just (-5)Like all other operators, <$> has a
fixity, which can be infixl, infixr,
or infix. The fixity defines how an expression like
abs <$> negate <$> Just 5is parsed.
If <$> were defined as infix, then
the above expression wouldn’t parse at all, giving an error like
Precedence parsing error
cannot mix ‘<$>’ [infix 4] and ‘<$>’ [infix 4] in the same infix expression
If <$> instead were defined as
infixr, then the above expression would parse as
abs <$> (negate <$> Just 5)meaning first apply negate to 5 inside
Just, and then go inside Just once more and
apply abs.
However, <$> is defined as infixl, so
that the applicative chains like
(+) <$> Just 5 <*> Just 6would parse correctly. This means that
abs <$> negate <$> Just 5is parsed as
(abs <$> negate) <$> Just 5which is probably not what you meant. Or is it?
It turns out that if you paste that expression (either parenthesized
or not) into ghci, you’ll get back Just 5 in all cases.
This happens because of the following instance defined in the base library:
instance Functor ((->) r) where
fmap = (.)(Note that (->) r is essentially the Reader monad, so
it’s not surprising that it’s a functor.)
So the expression
(abs <$> negate) <$> Just 5does make sense and is equivalent to
(abs . negate) <$> Just 5The operators * for which
a * (b * c) = (a * b) * c are called associative.
Usually associative operators have the type
a -> a -> a (either polymorphic of for a specific
a), so that both ways to put parentheses typecheck.
The <$> operator is curious because it doesn’t
have such a type and yet is associative:
f <$> (g <$> h) =
{- the definition of <$> -}
fmap f (fmap g h) =
{- the definition of (.) -}
(fmap f . fmap g) h =
{- the functor law -}
fmap (f . g) h =
{- the definition of <$> -}
(f . g) <$> h =
{- the Functor instance for (->) r -}
{- g has to be a function for the top expression to typecheck -}
(f <$> g) <$> h