Why Applicative Functor
Why Applicative Functor
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?
ghci> let a = fmap (+) [1,2,3]
ghci> :t a
a :: Num a => [a -> a]
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
[\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 (+)
.
ghci> fmap ($ 1) a
[2,3,4]
-- 2 = 1 + 1
-- 3 = 2 + 1
-- 4 = 3 + 1
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
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
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
magicOperator :: Maybe (a -> b) -> Maybe a -> Maybe b
If we abstract Maybe
with a general type constructor f
magicOperator :: f (a -> b) -> f a -> f b
Congratulations! You just derive the <*>
operator in Applicative Functor.
Although we can’t use fmap
directly here, we can combine it with pattern matching to achieve the same goal.
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
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
-- 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!
fmap g x1 :: f (t2 -> t3 -> ... -> tn -> t)
x2 :: f t2
Apparently, we should use <*>
here, and the equation should be like
fmap g x1 <*> x2 <*> x3 <*> x4 <*> ... <*> xn
-- or, more simpler
g <$> x1 <*> x2 <*> x3 <*> x4 <*> ... <*> xn
<*>
to apply a function within a Functor on a value within another Functor. The <*>
is kind of function applicationWhat is Applicative Functor
Now we know how <*>
works, let’s see the definition of Applicative Functor.
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
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.
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
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
($) :: (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 writef x
instead off $ x
<$>
is the infix form offmap
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
Wrap-up
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.