The blog post introduces an important typeclass, Applicative, in Haskell.

Applicative Functors
Applicative functors are an upgraded version of functors. If you are not confident about functors,
check out the last article, Road to Haskeller #13 - Functors. Let's think about using fmap
on a function
that requires two parameters, like fmap (*) (Just 3)
. What will happen? This will create a partial
function in Just
, like Just (* 3)
, which can be passed to another fmap
to perform further operations.
ghci> :t fmap (*) (Just 3)
fmap (*) (Just 3) :: Num a => Maybe (a -> a)
ghci> let b = fmap (*) (Just 3)
ghci> fmap (\f -> f 9) b
Just 27
However, if we do fmap (\f -> f (Just 9)) b
, it will not work as fmap
only applies functions that have
the same type as inside of a functor. To allow functions over another functor to be applied to a functor,
the Applicative
typeclass is defined in the Control.Applicative
module. The class is defined like the following.
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
The first part (Functor f) => Applicative
means f
needs to be a functor first for it to become Applicative
.
Hence, all the type constructors in the Applicative
typeclass are also functors, and we can use fmap
on them.
pure
is straightforward, as it creates an applicative functor with an input value inside it.
<*>
is an interesting function that takes a functor with a function inside of it and applies that function over a functor.
It means with <*>
, we can do something like the following.
ghci> (<*>) (Just (*3)) (Just 9)
Just 27
ghci> Just (*3) <*> Just 9
Just 27
Let's take a look at how Maybe
is a valid instance of Applicative
.
instance Applicative Maybe where
pure = Just
Nothing <*> _ = Nothing
(Just f) <*> something = fmap f something
We can see that pure
is defined with partial function application to wrap a value with Just
, and <*>
avoids operation
when Nothing
is provided while <*>
extracts a function from Just
and passes it to fmap
. Using these functions,
we can achieve interesting things like the following.
ghci> pure (+) <*> Just 3 <*> Just 5
Just 8
ghci> Just (*) <*> Just 3 <*> Just 5
Just 15
By using this setup from Applicative
, we can now use functions with multiple parameters on values in applicative functors
to generate partial functions or an output value. Alternatively to pure
and using the default value constructor and using <*>
,
we can use <$>
to perform fmap on a function and a first functor to create a functor for the subsequent operations.
ghci> (+) <$> Just 3 <*> Just 5
Just 8
The above is doing fmap (+) (Just 3)
to create Just (+ 3)
and using <*>
to perform operations inside of Maybe
.
Lists
A list is a valid instance of the Applicative
typeclass as well, which is implemented as follows.
instance Applicative [] where
pure x = [x]
fs <*> xs = [f x | f <- fs, x <- xs]
We can see from the above that list comprehension is utilized in defining <*>
. If you are not confident
about list comprehensions, check out Road to Haskeller #3 - Lists & Tuples. Similarly to Maybe
, we can do
something like the following.
ghci> [(*0),(+100),(^2)] <*> [1,2,3]
[0,0,0,101,102,103,1,4,9]
As we can see from the above, <*>
goes over the left list and performs the operation on each element
on the right list. We can understand the order of operations better by looking at the example below.
[(+),(*)] <*> [1,2] <*> [3,4]
--- Firstly, [(+1), (+2), (*1), (*2)] <*> [3, 4]
--- Then, [(3+1), (4+1), (3+2), (4+2), (3*1), (4*1), (3*2), (4*2)]
--- Result: [4, 5, 5, 6, 3, 4, 6, 8]
We can also use <$>
to do something like the following.
filter (>50) $ (*) <$> [2,5,10] <*> [8,10,11]
--- The above translates to:
--- filter (>50) $ [(*2), (*5), (*10)] <*> [8,10,11]
--- filter (>50) [(8*2), (10*2), (11*2), (8*5), (10*5), (11*5), (8*10), (10*10), (11*10)]
--- filter (>50) [16, 20, 22, 40, 50, 55, 80, 100, 110]
--- Result: [55, 80, 100, 110]
IO
IO
is also a valid instance of Applicative
, and the following is how the instance is implemented.
instance Applicative IO where
pure = return
a <*> b = do
f <- a
x <- b
return (f x)
The pure
function is simply return
as return
generates an IO action that does not do anything.
The <*>
function makes use of <-
syntax to extract the output of the IO actions to perform inner operations.
Then, it wraps the result with return
to get back to the IO action. This means the following refactoring can be performed.
--- From
myAction :: IO String
myAction = do
a <- getLine
b <- getLine
return $ a ++ b
--- To
myAction = (++) <$> getLine <*> getLine
Exercises
This is an exercise section where you can test your understanding of the material introduced in the article. I highly recommend solving these questions by yourself after reading the main part of the article. You can click on each question to see its answer.
Resources
- NA. Functors, Applicative Functors and Monoids. Learn You a Haskell for Great Good.