Applicative functors are more powerful than Functors but not as powerful as monads. They provide a way to partially apply a function inside an Applicative context to parameters also inside the same context.
Confused? Well hopefully after this article you won’t be.
As we saw earlier, a Functor is just a Haskell typeclass with the
single required fmap
function.
class Functor f where
fmap :: (a -> b) -> f a -> f b
We can apply a function from a -> b
to a value wrapped inside of a functor
context.
Now what if we want to apply a function to multiple arguments that are all
wrapped inside of a context? Such as summing two numbers wrapped in a Maybe
.
(Just 1) + (Just 2) -- ?
If we apply fmap
to Just 1
with the (+)
function we get back another
function wrapped in the Maybe
context.
ghci> :t (+) <$> Just 1
(+) <$> Just 1 :: Num a => Maybe (a -> a)
But now we are stuck. We need to apply a function inside of the Maybe context to a number inside the Maybe context.
This is where Applicatives come in!
They allow us to take a function which operates on normal unwrapped values, and apply it to parameters which are all wrapped in a context.
The typeclass definition requires all Applicatives to be Functors (meaning they
all implement the fmap
function).
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Additionally, an Applicative must implement the pure
and (<*>)
functions.
pure
just takes a normal unwrapped value and wraps it into an Applicative
context.
(<*>)
takes a function wrapped in an Applicative context and a parameter also
wrapped. It then unwraps both, applies a
to the function, and wraps the result
back up.
Lets see how it is implemented for the Maybe
type.
instance Applicative Maybe where
pure x = Just x
(Just f) <*> (Just x) = Just (f x)
_ <*> _ = Nothing
If either Maybe types are Nothing
then Nothing
is returned. Otherwise, each
value is unwrapped (via pattern matching), the function is applied to the
contents of second Maybe, and the result is wrapped back up as a Maybe.
Since all functions in Haskell are curried, we can partially apply a function of
n
arguments to n
parameters wrapped in the Applicative context.
Using our above example, summing two numbers wrapped in a Maybe would look like this
ghci> (+) <$> Just 1 <*> Just 2
Just 3
We first use <$>
to partially apply +
to Just 1
. Functor stuff. But then
we use <*>
to take the wrapped function and apply it to the wrapped parameter.
Alternatively we can use the pure
function to wrap (+)
inside of an
Applicative.
ghci> pure (+) <*> Just 1 <*> Just 2
Just 3
What happens if one of the parameters is Nothing
?
ghci> (+) <$> Just 1 <*> Nothing
Nothing
This is awesome! We don’t have to worry about checking whether or not our
parameters are valid. The (<*>)
function does that for us under the hood.