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
bind actually do.
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
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
map not do anything; and they can also contain more than one thing (in which case
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
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
Lwt.t in OCaml,
futures::future::Future in Rust. Note that in Rust, the
std::iter::Iterator trait implements a monad, where
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
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.
Promise.resolve, and it doesn't seem to exist in Rust.
open Core open Async (*  *) List.return 5 (* Some 5 *) Option.return 5 (* An immediately-available deferred computation containing "a" *) Deferred.return "a"
Promise.then(), and also combine it with
map(if the result is not a
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")
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
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 (
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))
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.
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:
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
bindon 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.