>
> {-# LANGUAGE GeneralizedNewtypeDeriving #-}
> module Monads where
One of the greatest features of Haskell is that it has programmable semicolon, i.e., the meaning of the semicolon is determined by the programmer.
For example, what is the meaning of the following function?
> compute e1 e2 =
> do { x <- e1
> ; y <- e2
> ; return $ x + y}
compute
uses e1
to pull the value x
uses e2
to drug the value y
and returns the sum of x
and y
.
What is the type of compute?
> compute :: (Num a, Monad m) => m a -> m a -> m a
e1
and e2
wrap values of type a
and since the last line is adding a
s, a
should be Numeric. Moreover, the wrapper m
should should instantiate Monad
s i.e., it should provide a method to pull a value and to return a value.
Since lists are Monads and Integers are Numerics, we can call compute
with a list of integers.
compute [1, 2] [3, 4] == [4, 5, 5, 6]
We got the sum of each pair combination!!!!
compute [1, 2] [3, 4] == [1 + 3, 1 + 4, 2 + 3, 2 + 4]
So, the way lists provide to pull their elements is give each one if them
Thus we can call compute
with Maybe values.
compute (Just 1) (Just 3) == Just 4
Pulling values from Just
values just returns the value. What about Nothing
?
compute Nothing (Just 3) == Nothing
compute (Just 1) Nothing == Nothing
Nothing does not have a value to give, so the computation just returns Nothing!
To define the semantics of the semicolon for each type constructor (like list or Maybe) you just need to define how the constructor instantiates the Monad class.
The class has just two methods, the bind and the return:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
Question: Have you seen the bind symbol >>=
before?
The bind says how to pull values out of the constructor and the return says how to wrap values in the constructor.
How do lists instantiate Monads? Just follow the types!
instance Monad [] where
-- return :: a -> [a]
return x = [x]
-- (>>=) :: [a] -> (a -> [b]) -> [b]
xs >>= f = concatMap f xs
concatMap f [] = []
concatMap f (x:xs) = f x ++ concatMap f xs
And how does this gives semantics to compute
?
Every time the Haskell compiler seems semicolon (or the do
notation) it applies the follow syntactic transformation
compute :: (Monad m, Num a) => m a -> m a -> m a
compute e1 e2 = compute e1 e2 =
do { x <- e1 -> e1 >>= \x ->
; y <- e2 e2 >>= \y ->
; return $ x + y} return $ x + y
What bind (>>=
) and return is the translation using? The ones that we defined for the specific monads!!!!
So, for our list example, compute [1, 2] [3, 4]
becomes
[1, 2] `concatMap` \x ->
[3, 4] `concatMap` \y ->
[x + y]
[1, 2] `concatMap` \x ->
[x + 3] ++ [x + 4] ++ []
[1, 2] `concatMap` \x ->
[x + 3, x + 4]
[1, 2] `concatMap` \x ->
[1 + 3, 1 + 4] ++ [2 + 4, 2 + 4] ++ []
[1, 2] `concatMap` \x ->
[5, 5, 5, 6]
Once again, to define the semantics of the maybe semicolon we just need to define the monadic instance.
instance Monad Maybe where
-- return :: a -> Maybe a
return x = Just x
-- (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= _ = Nothing
(Just x) >>= f = f x
Exercise: Remember the Err
type?
> data Err a = Error a | Value a
Make Err
an instance of Monad and use it to call the compute
function!
A probability is just a float number that should be between 0 and 1.
> data Prob = Prob Float deriving (Eq, Ord, Show)
We can now define Perhaps
, which represents a value with an associated probability:
> data Perhaps a = Perhaps a Prob
> deriving (Show)
Now, this is just a generalization of Haskell’s built-in Maybe
type, which treats a value as either present (probability 1
) or absent (probability 0
). All we’ve added is a range of possibilities in between: Perhaps x 0.5
represents a 50% chance of having a value.
> instance Monad Perhaps where
> return x = undefined
>
> (Perhaps x p) >>= f = undefined
Now you can use Perhaps
as a monad and the meaning of the semicolon is to multiply probabilities!!!!!
> pcompute = do
> x <- Perhaps 1 $ Prob 0.1
> y <- Perhaps 3 $ Prob 0.3
> return $ x + y
pcompute == Perhaps 4 (Prob 3.0000001e-2)
Magic!
What if you want to combine probabilities with another monad? Say that you want to have a list of probabilities and you want your bind operator (or the semicolon notation) to both pull each element of the list and multiply the probabilities? Turns out you can have this feature! Coming soon! Before that, let’s see how monads are used to fake effectful programming in Haskell!
instance Monad Perhaps where
return x = Perhaps x 1
(Perhaps x p) >>= f = Perhaps y (p * p')
where (Perhaps y p') = f x
> instance Num Prob where
> (Prob n1) + (Prob n2) = Prob (n1 + n2)
> (Prob n1) - (Prob n2) = Prob (n1 - n2)
> (Prob n1) * (Prob n2) = Prob (n1 * n2)
> abs (Prob n) = Prob (abs n)
> signum (Prob n) = Prob (signum n)
> fromInteger n = Prob (fromInteger n)
>