From Haskell in Depth by Vitaly Bragilevsky
In this article we’ll discuss a few ways that you can use monads to simplify code.
Take 40% off Haskell in Depth by entering fccbragilevsky into the discount code box at checkout at manning.com.
Haskell brings a concept of a monad to a level of practical use. In fact, we tend to think about any monad in terms of functionality it provides. Monads simplify our code significantly, they give the power of abstraction, and they guarantee the understanding of the code. In this article we’ll talk about using monads in practice. They help us implement difficult algorithms clearly and correctly, and they allow short, concise code and maintain readability and ease of support.
A teaser: Maybe monad as a line saver
What do you think about a function returning
Maybe a for some type variable
a? Well, it’s a computation which can have no result at all. The next question: what if you’ve several such computations and your task is to produce overall result from them? In general, there are two basic strategies:
- if you need both results to produce final one and one of the computations gives you
Nothingthen you answer with
- if it’s enough for you to get one result of the two then you stick with one which isn’t Nothing.
The choice of the right strategy clearly depends on your goals but Haskell gives you abstractions for both: the first strategy is implemented by monadic bind
Maybe monad and the second one—by monoid operation over
Imagine that you’ve two associative lists: one with person name and phone number pairs, and another with phone number and corresponding location pairs. How do you find location by the persons’ name? Clearly this scenario demands for the first strategy, because we can’t proceed without getting phone number first and we’re forced to return
Let’s implement this scenario in code. We’ve the following types:
type Name = String type Phone = String type Location = String type PhoneNumbers = [(Name, Phone)] type Locations = [(Phone, Location)]
We also have function
Prelude which is clearly a computation in
lookup :: Eq a => a -> [(a, b)] -> Maybe b
Now we can implement searching for location via
locByName :: PhoneNumbers -> Locations -> Name -> Maybe Location locByName pnumbers locs name = lookup name pnumbers >>= flip lookup locs
Although we’ve had to
flip arguments for
lookup to get a function suitable for monadic binding (
flip lookup locs subexpression has type
Phone → Maybe Location) the implementation looks quite concise and guarantees that we’ll get Just somelocation provided by two successful lookup calls.
An implementation without monadic bind looks more tedious:
locByName' :: PhoneNumbers -> Locations -> Name -> Maybe Location locByName' pnumbers locs name = case lookup name pnumbers of Just number -> lookup number locs Nothing -> Nothing
If you look at the implementation of
Maybe you’ll see the code with pattern matching over
Maybe a value to the left of
instance Monad Maybe where (Just x) >>= k = k x Nothing >>= _ = Nothing -- ...
We’ve saved a little of typing by reusing
>>=. This is all about it: we use monads for functionality and convenience they provide. The same idea works for
Applicative too as we normally try to use the functionality they provide whenever it’s sufficient for our goals.
For another example, let’s suppose you’re given a
String representing number, something like
"21". How do you double it? You must take special care here: what if
String is incorrect and doesn’t represent any number? Well, we can use
readMay :: Read a => String -> Maybe a
You don’t want to multiply
Nothing by 2 or you’ll cause the
Functor to not work:
doubleStrNumber :: (Num a, Read a) => String -> Maybe a doubleStrNumber s = (*2) <$> readMay s
It works as expected:
ghci> doubleStrNumber "21" Just 42 ghci> doubleStrNumber "yy" Nothing
If you’ve two
String-represented numbers and need to compute their sum, then it’s a job for
plusStrNumbers :: (Num a, Read a) => String -> String -> Maybe a plusStrNumbers s1 s2 = (+) <$> readMay s1 <*> readMay s2
And it also works as a charm:
ghci> plusStrNumbers "3" "5" Just 8 ghci> plusStrNumbers "3" "x" Nothing
If you want to see more, check out the book on Manning’s liveBook platform here.