Applicative Functors
The missing functionality of functors is obvious - fmap only works for functions that take in exactly 1 argument and applies that function to each individual element of a given data structure. But we might want to apply a function that takes multiple arguments - for example, how can we add together two values of the Maybe type? We could write our own function specific to that need:
1
maybeAdd :: Maybe Int -> Maybe Int -> Maybe Int
2
maybeAdd (Just x) (Just y) = Just (x + y)
3
maybeAdd _ _ = Nothing
4
​
5
ghci> maybeAdd (Just 5) (Just 3)
6
Just 8
Copied!
But that way we would have to write a new custom-made function for every function we would like to apply to two (or more) Maybe types - for example, multiplication.
This is where Applicative Functors (or Applicatives) come into play. Applicatives generalise applying pure functions to effectful arguments (such as the Maybe type) instead of plain values. The effect of the Maybe type is the possibility of failure, and we will explore the effects of some other data types later. The definition of Applicative is:
1
class (Functor f) => Applicative f where
2
pure :: a -> f a
3
(<*>) :: f (a -> b) -> f a -> f b
Copied!
Firstly, for a type to become an instance of Applicative , it must be an instance of the Functor class. The pure method is used to transform arbitrary values into the functor data structure f a. The (<*>) method is very similar to that of fmap , but in this case, the function being applied is itself wrapped into the functor data structure f (a -> b) - this is exactly what allows us to use currying and apply functions that take an unlimited number of arguments on Applicative data instances.

The Maybe Applicative

Let's look at how the Maybe type is made an instance of the Applicative class in the Prelude :
1
instance Applicative Maybe where
2
pure = Just
3
(Just f) <*> (Just x) = Just (f x)
4
_ <*> _ = Nothing
Copied!
pure wraps a value with the Just constructor, and (<*>) applies the function to the value if neither of the arguments has failed, otherwise, it results in Nothing. Now we can simply calculate the sum of two Maybe types without the need for defining a function:
1
ghci> pure (+) <*> Just 5 <*> Just 3
2
Just 8
Copied!
We first use pure on the addition function (+) in order to wrap it into a Maybe and then apply it to the two arguments. Let's take a closer look at how the application is executed and keep track of types:
1
-- pure transforms the (+) into an instance of an Applicative
2
ghci> :t pure (+)
3
pure (+) :: (Applicative f, Num a) => f (a -> a -> a)
4
​
5
ghci> :t pure (+) <*> Just 5
6
pure (+) <*> Just 5 :: Num a => Maybe (a -> a)
7
-- <*> applies the (+) function (wrapped into a Maybe) to the first argument
8
-- it returns a curried function wrapped in the Maybe constructor
9
​
10
ghci> :t pure (+) <*> Just 5 <*> Just 3
11
pure (+) <*> Just 5 <*> Just 3 :: Num b => Maybe b
12
-- finally, the curried function is applied to the second argument
13
-- and the addition is complete and results in a Maybe b
Copied!
The final result is of the type Maybe b so the underlying effect of the Maybe type - the possibility of failure - is handled by the applicative style of function application. In other words, we do not have to define any additional functions to handle specific Nothing cases, as they are already defined in the Applicative instance definition for Maybe:
1
ghci> pure (+) <*> Just 5 <*> Nothing
2
Nothing
3
​
4
ghci> pure (+) <*> Nothing <*> Just 3
5
Nothing
Copied!

The List Applicative

The List applicative is implemented in a way that function application through (<*>) applies the function in every possible combination of the arguments (as a Cartesian product in mathematics). So the underlying effect of the List type is the possibility of results. The Applicative instance declaration for List is:
1
instance Applicative [] where
2
pure x = [x]
3
fs <*> xs = [f x | f <- fs, x <- xs]
Copied!
With that, we can apply functions on List types through (<*>):
1
ghci> pure (+) <*> [1,2] <*> [3,4]
2
[4,5,5,6]
3
-- 1 + 3
4
-- 1 + 4
5
-- 2 + 3
6
-- 2 + 4
7
​
8
ghci> [(+10), (*10), (^2)] <*> [1,2,3]
9
[11,12,13,10,20,30,1,4,9]
Copied!
In the second example, each of the functions in the first list is a curried function that takes in one additional argument, and the result of the applicative action is applying all the functions from the first list to all the arguments of the second list.
For those curious, there is also an implementation that matches only one argument per function - ZipList (http://hackage.haskell.org/package/base-4.11.1.0/docs/Control-Applicative.html#t:ZipList)

The IO Applicative

The IO type refers to the impure world of Haskell, and its underlying effect is the ability to perform input/output actions. Therefore, the applicative instance of the IO type supports the application of pure functions to impure arguments, and can also handle sequencing and extraction of result values:
1
instance Applicative IO where
2
pure = return
3
a <*> b = do
4
f <- a
5
x <- b
6
return (f x)
Copied!
pure is simply our return function that wraps a pure value into an IO type, and given two impure arguments (IO actions), (<*>) performs the action a to get the function f and the action b to get the argument x , and finally returns f x - the result of that function application to the argument wrapped in the IO type.
As was mentioned before, the use of applicative style can handle both sequencing and extraction of values, so to define a function that reads two characters and returns their concatenation, instead of:
1
read2 :: IO String
2
read2 = do
3
a <- getLine
4
b <- getLine
5
return (a ++ b)
Copied!
we can simply write:
1
read2 :: IO String
2
read2 = pure (++) <*> getLine <*> getLine
3
​
4
ghci> read2
5
Applicative
6
Functors
7
"ApplicativeFunctors"
Copied!
Furthermore, it becomes much easier to create a function that reads an arbitrary n number of lines and concatenates them with applicative style and recursion:
1
getLines :: Int -> IO String
2
getLines 0 = return []
3
getLines n = pure (++) <*> getLine <*> getLines (n - 1)
4
​
5
ghci> getLines 5
6
1
7
2
8
3
9
4
10
5
11
"12345"
Copied!

Applicative Laws

There are four laws applicative functors must follow:
1
pure id <*> v = v -- Identity
2
pure f <*> pure x = pure (f x) -- Homomorphism
3
u <*> pure y = pure ($ y) <*> u -- Interchange
4
pure (.) <*> u <*> v <*> w = u <*> (v <*> w) -- Composition
Copied!
The Identity law states that applying the id function to an argument in applicative style returns the unaltered argument, much like we saw in functors.
The Homomorphism law states that pure preserves function application in the sense that applying a pure function to a pure value is the same as calling pure on the result of normal function application to that value (f x).
The Interchange law states that the order in which we evaluate components doesn't matter in the case when we apply an effectful function to a pure argument. The ($ y) is used to supply the argument y to the function u. A simpler example of using ($ y):
1
ghci> map ($ 2) [(2*), (4*), (8*)]
2
[4,8,16]
Copied!
The Composition law states that function composition (.) works with the pure function as well, so that pure (.) composes functions, i.e. composing functions u and v with pure (.) and applying the composed function to w gives the same result as to simply applying both functions u and v to the argument w.