Posted on March 27, 2016
by Taran Lynn
This is not a tutorial, it is just some of my thoughts. I expect the reader to
be familiar enough with monads to recognize if I’ve made a mistake. Please
contact me if you see any errors in this post.
Functions are Monads
Functions are monads, which also implies that they are applicative
functors. This can lead to some interesting computational models. Functions
implement the Monad class as
instance Monad ((->) r) where
return :: t -> (r -> t)
return = const
(>>=) :: (r -> t) -> (t -> (r -> s)) -> (r -> s)
f >>= g = \r -> g (f r) r
Where ((->) r) is similar in form to Map a . Both of these are defined to be
monadic over their second type argument. This means that (r -> t) is monadic
over t , meaning the types for return and bind ((>>=) ) have to be t -> (r -> t) and (r -> t) -> (t -> (r -> s)) -> (r -> s) , respectively. The first
signature is obviously the same as const , hence this is what return is
defined as. The operation of bind is, however, far more interesting. Consider
foo = do x <- id
y <- sqrt
return $ x + y
If we use this function we find that foo 4 is 6.0 , and foo 9 is 12.0 . On
closer inspection it appears that foo is a function that passes its argument
to id and sqrt and adds the two results. Or, in code
foo r = (id r) + (sqrt r)
To find out why this is let’s do some transformation on foo .
foo = do x <- id
y <- sqrt
return $ x + y
= id >>= (\x -> sqrt >>= (\y -> x + y))
= \r -> (\x -> (\r' -> (\y -> x + y) (sqrt r'))) (id r) r
= \r -> (\x r' -> (\y -> x + y) (sqrt r'))) (id r) r
= \r -> ((\y -> (id r) + y) (sqrt r))
= \r -> (id r) + (sqrt r)
In general, we find that if we replace id and sqrt with any functions f
and g , then
do x <- f
y <- g
return $ h x y
= \r -> h (f r) (h r)
Something even more interesting happens when we look at sqrt >>= (+) . This
function is exactly the same as foo . If we analyze it we see that it expands
to
do x <- sqrt
y <- (+) x
return y
Notice that y will expand to (+) x r , and x will expand sqrt r , which
when combined gives
(+) (sqrt r) r = (+) (sqrt r) (id r) = (sqrt r) + (id r)
A Practical Example
One example of the usefulness of this is
data Customer = Customer { getName :: String
, getEmail :: EmailAddr
, getAccountNumber :: Integer }
custComp :: Customer -> a
custComp = do name <- getName
email <- getEmail
accNum <- getAccountNumber
return $ someComputation name email accNum
Without using do notation, this would instead be
custComp :: Customer -> a
custComp cust = someComputation (getName cust) (getEmail cust) (getAccountNumber cust)
Functions as Applicative Functors
I find that using the fact that functions are applicative functors to also be
useful. First let us look at the trivial implementation of Functor for
functions.
instance Functor ((->) r) where
fmap :: (a -> b) -> (r -> a) -> (r -> b)
fmap = (.)
This isn’t particularly interesting until we consider the applicative
implementation.
instance Applicative ((->) r) where
pure = const
f <*> g = \x -> (f x) (g x)
The function foo can now be written as (+) <$> id <*> sqrt . The practical
example can also be written as
custComp :: Customer -> a
custComp = someComputation <$> getName <*> getEmail <*> getAccountNumber
I personally find this preferable to the two other ways of writing custComp ,
as it is both concise and informative.
Why Does This Matter?
Using functions in this way allows us to write algorithms that use the results
of applying the same argument to multiple functions in a clear, concise way.
As shown by the above image, we can write algorithms that are only concerned
with the outputs of f , g , and h , but still produces a function from x to
some result.
|