Lets define a simple type, `Maybe', which can store either a value or an absence of value:
data Maybe a = Just a | Nothing
deriving (Show)
And define a function which compares a number against a limit, and returns `Nothing' if the value is out of range: clamp :: Ord a => a -> a -> Maybe a
clamp limit x = if x > limit then Nothing else Just x
Easy enough. Now, how should the number be compared against multiple limits?One way is to just brute-force it:
multiclamp :: Ord a => a -> a -> a -> Maybe a
multiclamp lim1 lim2 x = case clamp lim1 x of
Nothing -> Nothing
Just x' -> clamp lim2 x'
But this is ugly. Lets use monads to simplify it.First, take a look at the core Monad operations. I'm going to use `build' and `bind' instead of `return' and `>>=', because they're probably more familiar to most readers.
class Monad m where
build :: a -> m a
bind :: m a -> (a -> m b) -> m b
Next, we'll define an instance of the `Monad' typeclass for `Maybe'. It will use the same semantics as the `multiclamp' example -- that is, if the first test returns `Nothing', the entire computation will return `Nothing': instance Monad Maybe where
build x = Just x
bind Nothing _ = Nothing
bind (Just x) f = f x
Using this definition, `multiclamp' can be written thus: multiclamp2 :: Ord a => a -> a -> a -> Maybe a
multiclamp2 lim1 lim2 x = bind
(clamp lim1 x)
(\x' -> clamp lim2 x')
Although ugly, this notation is sufficiently generic that the compiler can generate it from an imperative notation. Lets look at two more examples. First, `multiclamp2' written using "real" monads. Notice that this is exactly the same as before, except `bind' is an operator named `>>=': multiclamp3 :: Ord a => a -> a -> a -> Maybe a
multiclamp3 lim1 lim2 x =
(clamp lim1 x) >>=
(\x' -> clamp lim2 x')
And finally, using the imperative-style `do' notation. Note that although this is imperative, it is not impure -- this is semantically equal to `multiclamp3': multiclamp4 :: Ord a => a -> a -> a -> Maybe a
multiclamp4 lim1 lim2 x = do
x' <- clamp lim1 x
clamp lim2 x'
This is, obviously, a superficial example -- you'd never see such verbose code in code written by an experienced programmer. But the verbosity helps to visualize what the compiler is doing.For completeness, here's a "cleaner" version, which takes advantage of currying and the combinator `.':
multiclamp5 :: Ord a => a -> a -> a -> Maybe a
multiclamp5 lim1 lim2 = (clamp lim2 =<<) . clamp lim1