Archive for the ‘Monad’ Category

Functors and Monads (containers)

June 2, 2009

Introduction

In a philosophy class in college, I remember learning the idea that in order to understand something, you can’t simply study it. For example, if you study the heart, you’ll understand the function of the valves, what causes the muscle to relax and contract, and so on, but you’ll have no idea what a heart is and what its purpose is. In order to learn that, you need to study the human body.

I’m going to take the same approach to Monads and Functors (and hopefully later Applicative). I hope this doesn’t turn in another Monads are Burritos. I’ll avoid as much as possible type signatures and such so as not to obscure the main point.

Containers

Two scary concepts in Haskell, Functors and Monads, both deal with containers. These aren’t as limited as containers from a language like Java. There, containers are linked lists, arrays maps, sets; some way of managing collections of stuff. In Haskell, we use containers to represent actions, logging, and- our topic today- things which might exist. I’m speaking about Maybe.

I’ve chosen Maybe since I find it easy to think about. You either have Just a result, or Nothing. To further simplify, we’ll only deal with Maybe Int; a number that might exist.

How can such a thing occur? Let’s say these numbers represent the price for an item, given in cents. We might have a getPrice function which takes an Item as an argument. If a Toothbrush costs $1.25, then the function would return Just 125.

On the other hand, say you don’t have a price for Toothpaste. Then the function would return Nothing. (Think for a moment how you’d deal with this in Java, should a 0 represent unknown? What if you have a buy one-get one free sale? Use -1? You’ll have to remember to check for it everywhere. But I digress.)

Monads

Anyway, let’s now say that you have another function to add tax onto the purchase price. It doesn’t deal with Maybes; it adds 5% to an Int. But our getPrice function returns a Maybe Int. How can we stick these two functions together (ie, compose them)?

This is where we can use the trusty old Monad. Let’s start with do notation:

getPriceWithTax item = do
    price <- getPrice item
    return $ addTax price

That’s a little verbose for something so simple. Let’s drop the do:

getPriceWithTax item = getPrice item >>= return . addTax

Great… except why the return? We have two types at play: Int and Maybe Int. When dealing with one-argument functions, you get four possible function signatures:

  1. Int -> Int
  2. Int -> Maybe Int
  3. Maybe Int -> Int
  4. Maybe Int -> Maybe Int

The third option is unwrapping a contained value; it is not a topic for Monads or Functors, so ignore it for now. (If you care, look up fromJust.) The first option is a regular old function, like addTax. Option 2 is like getPrice: it ends up wrapping a container. (We’ll address option 4 later.)

Composition

In order to compose two functions, the output of one must be the same type as the input of the other. With containers, we in general can only add containers, not take them away. So in our getPriceWithTax function above, we have getPrice returning a Maybe Int, and addTax taking a plain Int.

To make them compatible, one of them will have to change. Guess which? That’s right, we need to make addTax take a Maybe Int instead of an Int. Also, since we can’t remove the container in the middle, we end up returning a Maybe Int as well, which leads us to option 4 above.

Creating an option 4 function is where the Monadic bind function (>>=) comes in handy. It converts an contained -> contained function into a contained -> contained one. This gives us back composibility! But wait: addTax returns an uncontained value. No problem: we just add a return to make addTax return contained values.

Functors FTW

You might be thinking that it’s kind of silly to do business this way, and I’ll agree with you. What we really want is a way to convert totally uncontained functions (option 1) to totally contained ones (option 4). This is what Functors are for. A Functor has a single function, fmap, which does exactly that. So our code becomes:

getPriceWithTax3 item = addTax `fmap` getPrice item

Monads are still good

Now, there are times- many of them- when you’ll need the full power of Monads to deal with things. I hope to address that in a future post. But for now, the moral is: if you are using return with Monadic bind, you might want to consider fmap instead.

Advertisements