ARTICLE

Monads as Practical Functionality Providers

From Haskell in Depth by Vitaly Bragilevsky

Manning Publications
4 min readJan 14, 2022

--

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 Nothing then you answer with Nothing;
  • 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 >>= for Maybe monad and the second one—by monoid operation over Maybe values.

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 Nothing.

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 lookup from Prelude which is clearly a computation in Maybe monad:

lookup :: Eq a => a -> [(a, b)] -> Maybe b

Now we can implement searching for location via >>= for Maybe a:

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 >>= for 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 Functor and 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 from Safe module:

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 Applicative:

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.

--

--

Manning Publications

Follow Manning Publications on Medium for free content and exclusive discounts.