Why Applicative Functor

Info
I assume that you have a good understanding of Functor before reading this article.

There is a crucial question before we delve into the Applicative Functor: Why do we still need the Applicative Functor when we already have Functor?

The signature of fmap function in Functor is a -> b -> f a -> f b. So we can only use regular unary function a -> b as a mapping function. What if we want to use a mapping function with multiple arguments? For example, we may want to use (+) :: a -> a -> a as the mapping function, then what we will get?

haskell

ghci> let a = fmap (+) [1,2,3]
ghci> :t a
a :: Num a => [a -> a]
Info
The Unary function is a function that only has one single argument

From the output of ghci, we can see each value in the Functor becomes a function a -> a. Given that every function in Haskell is a currying function, we can figure out how it works. Each value in the List Functor becomes the first argument of (+). You may imagine the List Functor will be

haskell

[\x -> 1 + x, \x -> 2 + x, \x -> 3 + x]
-- or, more simpler
[(1+), (2+), (3+)]

So we should have no problem using 1 as the second argument of (+).

haskell

ghci> fmap ($ 1) a
[2,3,4]
-- 2 = 1 + 1
-- 3 = 2 + 1
-- 4 = 3 + 1
Note
Conclusion: If the mapping function f contains two arguments, then fmap f x will make each value within Functor x become a function.

If you understand the above example, I’m sure that you know how to get Just (1+) using fmap

haskell

ghci> let justF = fmap (+) $ Just 1
ghci> :t justF
justF :: Num a => Maybe (a -> a)

Now imagine that we have a value of Just 2

haskell

ghci> :t Just 2
Just 2 :: Num a => Maybe a

Then, can we combine Just (1+) and Just 2 somehow and get Just (1+2) = Just 3? After all, it should be a natural thing to do.

You will find that we can’t directly use fmap to achieve this. The reason is that the fmap can only use a regular unary function a -> b as the mapping function. However, the function now lives in a Functor (that is, Maybe (a -> a) instead of a -> a). If such an operator exists (denoted as magicOperator), we can derive its signature

haskell

magicOperator :: Maybe (a -> b) -> Maybe a -> Maybe b

If we abstract Maybe with a general type constructor f

haskell

magicOperator :: f (a -> b) -> f a -> f b

Congratulations! You just derive the <*> operator in Applicative Functor.

Tip

Although we can’t use fmap directly here, we can combine it with pattern matching to achieve the same goal.

haskell

apply Nothing _ = Nothing
apply (Just f) Nothing = Nothing
apply (Just f) (Just v) = fmap f (Just v)

However, other Functor types don’t necessarily support extraction using pattern matching like this :(


Previously, we only considered the scenario where the mapping function has one argument. Now let’s consider the scenario with n arguments. Let the mapping function be represented as g, and its type signature is

haskell

g :: t1 -> t2 -> t3 -> ... -> tn -> t

Let’s assume that we have a bunch of Functors xi, where each xi has the type f ti. Since Haskell functions are all curried, we can start by deriving from fmap g x1

haskell

-- Note that 
--  1. fmap :: (a -> b) -> f a -> f b
--  2. g :: t1 -> t2 -> t3 -> ... -> tn -> t
--  3. x1 :: f t1
fmap g x1 :: f (t2 -> t3 ->  ... -> tn -> t)

Let’s continue to derive the type of fmap g x1 x2. And we will be stuck here because the types just don’t match!

haskell

fmap g x1 :: f (t2 -> t3 ->  ... -> tn -> t)
x2        :: f  t2

Apparently, we should use <*> here, and the equation should be like

haskell

fmap g x1 <*> x2 <*> x3 <*> x4 <*> ... <*> xn
-- or, more simpler
g <$> x1 <*> x2 <*> x3 <*> x4 <*> ... <*> xn
Note
Conclusion: We need the Applicative Functor because we need to use <*> to apply a function within a Functor on a value within another Functor. The <*> is kind of function application
Warning
In addition to the following definitions, the Applicative Functor should also satisfy Applicative Laws

Now we know how <*> works, let’s see the definition of Applicative Functor.

haskell

class (Functor f) => Applicative f where
  pure  :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

Let’s focus on the pure function. The type a -> f a indicates that the pure function just put a value a into the Applicative Functor. The most intuitive application may be putting a function into an Applicative Functor. In the aforementioned example, we have

haskell

fmap g x1 <*> x2 <*> x3 <*> x4 <*> ... <*> xn
-- or, more simpler
g <$> x1 <*> x2 <*> x3 <*> x4 <*> ... <*> xn

We can use the pure function and rewrite the equation.

haskell

pure g <*> x1 <*> x2 <*> x3 <*> x4 <*> ... <*> xn

The <*> was derived earlier by ourselves, and its meaning is already clear, so we won’t go into detail here.

Let’s see a concrete example of Applicative Functor. The Maybe type is an Applicative Functor

haskell

instance Applicative Maybe where
    pure                  = Just
    (Just f) <*> (Just x) = Just (f x)
    _        <*> _        = Nothing

Let’s compare three similar but different operators that all represent function application

haskell

($)   ::                    (a -> b) -> a -> b
(<$>) :: Functor f     =>   (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b

Where

  • ($) is a navie function application, which can apply a regular unary function on a regular value. However, we usually write f x instead of f $ x
  • <$> is the infix form of fmap function, which can apply a regular unary function on a Functor.
  • <*> is the function application operator of Applicative Functor, which can lift a regular unary function into Applicative Functor and apply it on an Applicative Functor

This article discusses why, in addition to Functor, we also need Applicative Functor. In my opinion, the greatest utility of Applicative Functor is that we can easily lift any ordinary function into an Applicative Functor and then use <*> for function application.