Introduction

This is the main thing you need to know. A Monad is a type which has this operation called bind ( among others, but we’ll keep it simple) defined. Here are the three parts:

  1. m a - a container of a value of type a.
  2. ( a -> m b) - a function that takes an a and puts it in the container m and potentially converts the type inside the container to a new type b, although a can equal b, so bind doesn’t have to convert the type inside to another type.
  3. m b - a new container updated with the value produced by applying (2) to the value of type a inside (1).

Bind is just a way for you to update what’s inside a monad. m remains constant, but the type which it contains a changes to b. If we had an operation op with a type signature like op :: m a -> (a -> n b) -> n b, it would both convert the container type from m to n as well as the type of what the container contains from a to b. But don’t worry! With bind we are leaving the container type constant!

It’s important to remind ourselves what we are focusing on here. We are reasoning about the type of bind. We aren’t thinking about how bind actually does what it does in terms of specific operations on the types involved. Programming in Haskell often requires an interchange of reasoning on two levels:

  1. Reasoning at the type level: We think in general about the shape of our functions in terms of the types they operate on and what they evaluate to (the last term in the type signature).
  2. Reasoning at the implementation level: Eventually we must leave the world of pure reason and actually think about what operations must be performed in order to produce the desired type.

This is a light weight post, so I’m not going to go into all the rich details needed to fully understand the implications of what I said above, but I would like to at least show you a single case of bind in action by using the list monad to create a list filter.

Application - Filtering a List

[34,2,3,14] >>= \x -> if x > 10 then [x] else []

When we execute this code, we get: [34,14]. Let’s look at the terms in this expression.

  1. [34,2,3,14] - an object of type [Int]. Lists are monads, i.e., they provide an definition for bind and the other monad operators, and we can rewrite the type of this object as [] Int. So we see that [] lines up with m and Int lines up with a in the first argument m a of the bind operator.
  2. >>= - the bind operator. We know that the left hand side of this operator has the proper type [] Int ~ m a, so things are looking good so far, but what about the right hand side (3)?
  3. \x -> if x > 10 then [x] else [] - a function which takes an x and puts it in the same type of box as the elements of (1) – namely a list. So we know this has the type of (a -> m b), which when we substitute the type variables for concrete types becomes: Int -> [] Int. So we have a valid use of the bind which, when we substitute the type variables for the particular example we have, has the type: [] Int -> ( Int -> [] Int ) -> [] Int ~ m a -> ( a -> m b) -> m b. Let’s now review the internals of this lambda function.
    1. x > 10 - a filter predicate. If it is true we return (3.2), otherwise we return (3.3).
    2. [x] - we put our value in the list box when it satisfies our filter predicate (3.1).
    3. [] - if our value does not satisfy our filter predicate (3.1), then we return an empty box.

Exercises

  1. How would you use this to create your own filter :: (a -> Bool) -> [a] -> [a] function?
  2. Can concat :: Foldable t => t [a] -> [a] and map :: (a -> b) -> [a] -> [b] be used to create a filter function?
  3. Instances of monads also define return :: Monad m => a -> m a. How would we use this in place of [x] in (3.2)?
Experiment with this code on Repl.it