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 b
For 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 5
is 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 6 (
would parse correctly. This means that
abs <$> negate <$> Just 5
is parsed as
abs <$> negate) <$> Just 5 (
which 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 5 (
does make sense and is equivalent to
abs . negate) <$> Just 5 (
The 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:
<$> (g <$> h) =
f {- 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 <$> -}
. g) <$> h =
(f {- the Functor instance for (->) r -}
{- g has to be a function for the top expression to typecheck -}
<$> g) <$> h (f