My recent post about OCaml inline operators had a section about the monad operators, but I think it would also be useful to talk about what a monad is, and what map and bind actually do.

Overview

From a non-theoretical engineer perspective, a monad is a kind of container that you can apply functions to to get a new monad. In particular, you need a way to put something into your monad and a way to apply changes to it. These are called return and bind. There is also a third operation, map, which is a combination of the two. Note that monads are allowed to be empty, which will make bind and map not do anything; and they can also contain more than one thing (in which case bind and map will call the function you give them more than once and merge the results).

The most common use of monads is probably in lists and arrays. You've probably already seen List.map or Array.map (most languages have them). If you use a language with an "option" or "maybe" type (sort-of like a nullable type), they probably provide a monadic interface for that too.

Monads are also becoming more and more popular for asynchronous code. Examples include Promise in JavaScript, Deferred.t and Lwt.t in OCaml, futures::future::Future in Rust. Note that in Rust, the std::iter::Iterator trait implements a monad, where Iterator.map is bind, and Iterator.flat_map is map.

Return, bind and map

I've chosen to use the named functions for this example, but in real OCaml code it's common to see the infix operators >>= for bind, and >>| or >|= for map. In one case, the reverse application operator (|>) was necessary to make the code even remotely readable (x |> f is the same as f x). See OCaml inline operators for details.

  • return takes a value and wraps it in a monad. JavaScript Promises call this Promise.resolve, and it doesn't seem to exist in Rust.

    open Core
    open Async
    
    (* [5] *)
    List.return 5
    
    (* Some 5 *)
    Option.return 5
    
    (* An immediately-available deferred computation containing "a" *)
    Deferred.return "a"
    
  • bind unwraps the monad, applies an operation, and expects a new monad as a result. JavaScript Promises call this Promise.then(), and also combine it with map (if the result is not a Promise, JavaScript will act like it was wrapped with Promise.resolve).

    open Core
    open Async
    
    (* [5 ; 6 ; 10 ; 11] *)
    List.bind [5 ; 10] ~f:(fun v -> [ v ; v + 1 ])
    
    (* Empty list [] *)
    List.bind [5 ; 10] ~f:(fun _ -> [])
    
    (* Some 6 *)
    Option.bind (Some 5) ~f:(fun v -> Some (v + 6))
    
    (* None *)
    Option.bind (Some "a") ~f:(fun _ -> None)
    
    (* In the case of `None`, there's nothing to unwrap so `bind` doesn't
       do anything and this returns `None` *)
    Option.bind None ~f:(fun _ -> Some "example")
    
    (* Opens example.txt, returning a deferred computation monad containing
       the lines of the file, converts that to a new deferred computation
       monad containing the reversed lines of the file, then writes those
       lines to a file. `bind`ing the result of this will cause another
       computation to happen after the new file is written. *)
    Reader.file_lines "example.txt"
    |> Deferred.bind ~f:(fun lines -> return (List.rev lines))
    |> Deferred.bind ~f:(Writer.save_lines "reversed.txt")
    
  • map is like bind, except the monad wrapping is automatic (as if you had called return). This is useful when you want to call normal (non-monad-returning) functions on the contents of your monad.

    Most of these are equivalent to the examples under bind:

    open Core
    open Async
    
    (* [6 ; 11] *)
    List.map [5 ; 10] ~f:((+) 1) (* `(+) 1` is the same as `fun v -> 1 + v` *)
    
    (* Some 6 *)
    Option.map (Some 5) ~f:((+) 1)
    
    (* It's as if we did `return None` here, so we get a
       two layers of option monads, and the result is `Some None` *)
    Option.map (Some "a") ~f:(fun _ -> None)
    
    (* None *)
    Option.map None ~f:(fun _ -> "example")
    
    Reader.file_lines "example.txt"
    |> Deferred.map ~f:List.rev
    |> Deferred.bind ~f:(Writer.save_lines "reversed.txt")
    

In real languages, you're likely to find significantly more helpers than this. For example, in OCaml, pretty much every monad has an iter function, which is like bind except you don't return anything (for example, if you want to print the contents of a monad).

There's also usually a way to combine monads (List.append, Option.both, Deferred.all, etc.). Note that a both function can be trivial:

(** takes two monads and returns a monad with a tuple of their values
    (or the empty monad if either of them are empty) *)
let both a_monad b_monad =
   bind a_monad ~f:(fun a_value ->
     bind b_monad ~f:(fun b_value ->
       a_value, b_value))

Why monads?

My experience has been that monads in general are harder to misuse than equivalent iterative or callback-based code, and async monads in particular are much easier to compose than callback-based async code.

For example, List and Option monads make it so you don't have to worry about bounds checking or nulls. If your List is empty, map won't do anything. If your Option is null, map will skip it.

The other somewhat recent change is that every language is using async monads these days, because it turns out callbacks are terrible. There are are few major benefits here:

  • You can't forget to return an async monad. If you're in a statically typed language like OCaml, forgetting to return something is a type error, and in a dynamic language like JavaScript, it's equivalent to returning a monad containing undefined (which is probably not what you meant to do, but in practice is usually much easier to debug than code where a callback was missed). While it's technically possible, I'm not aware of any language that statically guarantees that you call a callback in all brances of a function, so monads are a huge win here.

  • It's much easier to do multiple things at the same time with monads than with callbacks. Good luck writing an equally efficient version of this with callbacks:

    (* Read email content, email list and connect to an SMTP server at the
       same time *)
    let db = db_connect () in
    let content = Deferred.bind db ~f:load_email_content in
    let emails = Deferred.bind db ~f:load_emails in
    let smtp_connection = smtp_connect () in
    Deferred.all [ content ; emails ; smtp_connection ]
    >>| fun [ content ; emails ; smtp_connection ] ->
    List.map emails ~f:(send_email smtp_connection content)
    |> Deferred.all
    

    Note: I've tried to avoid overly OCamly code in this, but realistic OCaml code would have a lot less boilerplate by using the >>= bind operator and ppx_let:

    let db = db_connect () in
    let%bind content = db >>= load_email_content
    and emails = db >>= load_emails
    and smtp_connection = smtp_connect () in
    Deferred.List.iter emails ~f:(send_email smtp_connection)
    
  • You can hold onto a monad in a variable. This is incredibly useful for caching, since you can start a download / start reading a file / whatever, and then immediately cache the async monad. Once the operation is done, calling bind on it will be nearly-instant, and you don't have to deal with many of the fun problems of threadsafe caches.

    We use this in FeedReader to load favicons exactly once (with a fairly complicated loading process involving a local on-disk cache, HTTP requests, and error conditions).

Hopefully this is helpful to anyone else who is new to monads and wondering what the big deal is.