Functors, Applicatives and Monads
The main type classes is what I think is the hardest part to understand in Haskell. Its difficult to understand the purpose of each type class e.g. Monads, Applicatives and Functors. And its difficult to understand why the laws of each type class should apply.
There are many good references which explain this much better than I do here. This is just for me ...
Problem 1: I want to apply a function of arbitrary length on values that have a context. In this example the arbitrary length of the function add3
is 3.
let add3 = \a b c -> a + b + c
p0 = Just 1
p1 = Just 2
p2 = Just 3
:t add3
add3 :: Integer -> Integer -> Integer -> Integer
If I had pure values the calculation would be trivial.
add3 1 2 3
=> 6
Just 1
is a Maybe
type class which is an instance of Functor
, Applicative
and Monad
.
So lets try to perform the calculation using the Functor
type class. Functor
has the ability to map a function (fmap
) on a single value in an context and return the result in the same context.
:t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
:t fmap add3 (Just 1)
fmap add3 (Just 1) :: Maybe (Integer -> Integer -> Integer)
So fmap
is able to partially apply add3
to 1
and embed it in a Maybe
context.
fmap
takes a pure functions. So to be able to apply the partial function to next argument (Just 2
) we would need to extract the pure function from the Maybe
context again. This could of course be done.
let fromJust (Just x) = x
So this could be used to apply fmap
again
:t fmap (fromJust $ fmap add3 (Just 1)) (Just 2)
fmap (fromJust $ fmap add3 (Just 1)) (Just 2) :: Maybe (Integer -> Integer)
And again
fmap (getPure $ fmap (getPure $ fmap add3 (Just 1)) (Just 2)) (Just 3)
Just 6
But, this isn't especially readable. Applicative
have a much more convenient function which would simplify things radically. <*>
does specifically what we want. It takes a function in a context and applies that to a value in a context.
:t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Because the partially applied function we got from the first application of fmap
on the Maybe
value is in a context this can be used directly.
:t fmap add3 (Just 1) <*> (Just 2)
fmap add3 (Just 1) <*> (Just 2) :: Maybe (Integer -> Integer)
This returns the second partial application of add3
in a context so it can be used again to finalize the calculation. Note that <*>
is left assiciative.
fmap add3 (Just 1) <*> (Just 2) <*> (Just 3)
Just 6
This is much simpler than alternating between fromJust
and fmap
to get to the result!
Applicative
also provides a function to put a primitive value in context called pure
.
:t pure
pure :: Applicative f => a -> f a
So if we use pure
to put add3
in contect we have an alternative way to perform the calculation.
pure add3 <*> (Just 1) <*> (Just 2) <*> (Just 3)
Just 6
The fact that pure add3 <*> (Just 1) = fmap add3 (Just 1)
is actually one of the laws of applicative functors.
pure f <*> x = fmap f x
Because pure f <*> x
is so commonly used, a helper function is defined which does both these operation in one go.
:t (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<$>) f x = pure f <*> x -- OR
(<$>) f x = fmap f x
:t add3 <$> (Just 1)
add3 <$> (Just 1) :: Maybe (Integer -> Integer -> Integer)
:t fmap add3 (Just 1)
fmap add3 (Just 1) :: Maybe (Integer -> Integer -> Integer)
:t pure add3 <*> (Just 1)
pure add3 <*> (Just 1) :: Maybe (Integer -> Integer -> Integer)
So the same expression can be written in a even more readable way.
add3 <$> (Just 1) <*> (Just 2) <*> (Just 3)
Just 6
From what we have seen, using Applicatives we can perform a set of computations in a pre-defined order i.e. the partial application of add3
on each of the values in context (from left to right). What if the sequence of operations isn't known from the beginning i.e. if one of the computations return different results (e.g. because its impure).
Instead of the pre-defined values (like Just 1
), I have defined an IO operation (getNum
) that takes a string as input from the user and convert that string to an integers. Lets try it.
getNum
1
1
getNum
text
*** Exception: Prelude.read: no parse
Yes, it chokes if I dont enter an integer.
So how is getNum
defined? getLine
takes user input and return IO String
. IO String
is a monad type class to encapsulate IO in a pure context. An IO String
value is non-determined until the program executes and the actual value is retrieved from the user.
:t getLine
getLine :: IO String
getNum
should return the number and not the string so I need to convert the input. The pure function read
does that for us. Read convert a sting to any type a
. To make it understand what to convert to we need to supply a type signature for the expected result.
:t read
read :: Read a => String -> a
:t (read :: String -> Integer)
(read :: String -> Integer) :: String -> Integer
A monad type class is similar to applicatives in that it holds a value in a context. The different context is specified by its specific set of functions and laws that must apply for every monad. Functors, Applicatives and Monads are all related. All Monads are Applicatives which in turn are Functors.
One important function of the monad class is the bind
function (>>=
).
:t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b
As you can see it is quite similar to fmap
and <*>
. While fmap
takes a pure function applied to a value with context and <*> takes the same function in a context and applies to the same value in context, >>=
takes a function which takes a pure value and return the result in a context and apply that function to a value with a context. The order of arguments is also reversed.
So how do we create a function that takes a pure value and return that value in a context. Using pure
would be one way.
let f a = pure a
Prelude Control.Applicative> :t f
f :: Applicative f => a -> f a
Monad type class define return
which is synonymous to pure
.
:t return
return :: Monad m => a -> m a
So, if we use getLine
it will generate a string in a IO context. If we define a function that takes that pure string value, convert it to an integer and return that integer in a new IO context, we should be able to use >>=
to bind these two operations together as a single computation.
:t (return . (read :: String -> Integer))
(return . (read :: String -> Integer)) :: Monad m => String -> m Integer
let getNum = getLine >>= (return . (read :: String -> Integer))
:t getNum
getNum :: IO Integer
getNum
is similar to Just 1
as they are both values in a context, however somewhat different contexts.
Lets see how the IO monad works as applicatives.
Prelude Control.Applicative> add3 <$> getNum <*> getNum <*> getNum
1
2
3
6
Yes, that works as expected.
This is the essence on how impure functions can be mixed with pure functions. It is still the pure add3
functions that we use to calculate the sum of the impure results from getNum
. It is getNum
that must be validated with extra concern (as it is impure), not the whole composed operation.
So far we assume that user always input is 3 integers and nothing else. What if we were to execute completely different results depending on user input. This is not very uncommon (and trivial in an imperative language).
Problem 2: I want collect numbers from the user and sum them, until user enter q
This impossible to do with applicatives or functors. Using monads it is possible.
The bind function can return different monadic values depending on the value entering the monadic function or the result of the computation itself.
To be able do perform IO computations the monadic values must be of type IO a
. The result of this computation is a sum of integers, so the monadic type should hold the sum in an IO context (IO Integer
).
Hence, the monadic computation takes the current sum as input and retrieves a new integer from the user. If user enter q
the current sum is returned. If a number is entered, current monadic function is bound to itself with the updated sum as input.
getNum :: Integer -> IO Integer
getNum acc = do
s <- getLine
if s == "q" then return acc
else getNum $ acc + (read s :: Integer)
The main function is trivial. We just call getNum
with sum set to 0 and print the result. Note that getNum 0
will result in an unknown set of computations.
main = do
res <- getNum 0
putStrLn $ show res
To see how do
maps to the bind (>>=)
function we can de-sugar the main function.
main = getNum 0 >>= (\res -> putStrLn $ show res)
This composition of getNum
depending on user input, cannot be implemented using applicatives. With applicatives the execution of each computation is ordered, but the number of computations must be explicit by the code. With monadic values and the composition using bind this is not necessary.
The result of the computation can be a sum of 0 or more computations depending on user input.
./sum_test
1
2
3
q
6
Some other poking around
> :t (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b
> (+1) <$> Just 1
Just 2
> :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
> Just (+1) <*> Just 1
Just 2
> [(+1),(+2)] <*> [1,2]
[2,3,3,4]
> (+) <$> [1,2] <*> [10,20]
[11,21,12,22]
> fmap (+) [1,2] <*> [10,20]
[11,21,12,22]
instance Applicative ((->) r) where
pure x = (\_ -> x)
f <*> g = \x -> f x (g x)
let ap f g = \x -> f x (g x)
> :t ap
ap :: (t2 -> t1 -> t) -> (t2 -> t1) -> t2 -> t
> ap (+) (+2) 1
4
Note f
in ap f g
takes two parameters and g one. So the calculation is
(+) 1 ((+) 2 1) = 4
> replicate 3 1 >>= replicate 2
[1,1,1,1,1,1]
> [1] >>= replicate 3 >>= replicate 2
[1,1,1,1,1,1]
I don't understand where this is defined. There is a pattern match fail in the second example. Why exception in the translated case?
> do { [a] <- Just [2]; return a } :: Maybe Int
Just 2
> do { [a] <- Just [1,2]; return a } :: Maybe Int
Nothing
> Just [1] >>= (\[a] -> return a) :: Maybe Int
Just 1
> Just [1,2] >>= (\[a] -> return a) :: Maybe Int
*** Exception: <interactive>:104:17-32: Non-exhaustive patterns in lambda