Simple introduction to monads in OCaml posted November 2022
I'm not a language theorist, and even less of a functional programming person. I was very confused trying to understand monads, as they are often explained in complicated terms and also mostly using haskell syntax (which I'm not interested in learning). So if you're somewhat like me, this might be the good short post to introduce you to monads in OCaml.
I was surprised to learn that monads are really just two functions:
module type Monads = sig type 'a t val return : 'a -> 'a t val bind : 'a t -> ('a -> 'b t) -> 'b t end
Before I explain exactly what these does, let's look at something that's most likely to be familiar: the
An option is a variant (or enum) that either represents nothing, or something. It's convenient to avoid using real values to represent emptiness, which is often at the source of many bugs. For example, in languages like C, where
0 is often used to terminate strings, or Golang, where a
nil pointer is often used to represent nothing.
option has the following signature in OCaml:
type 'a option = None | Some of 'a
Sometimes, we want to chain operations on an option. That is, we want to operate on the value it might contain, or do nothing if it doesn't contain a value. For example:
let x = Some 5 in let y = None match x with | None -> None | Some v1 -> (match y with | None -> None | Some v2 -> v1 + v2)
the following returns nothing if one of
None, and something if both values are set.
Writing these nested match statements can be really tedious, especially the more there are, so there's a
bind function in OCaml to simplify this:
let x = Some 5 in let y = None in Option.bind x (fun v1 -> Option.bind y (fun v2 -> Some (v1 + v2) ) )
This is debatably less tedious.
This is where two things happened in OCaml if I understand correctly:
- Jane street came up with a ppx called ppx_let to make it easier to write and read such statements
- OCaml introduce the same feature without ppxs
Let's explain the syntax introduced by OCaml first.
To do this, I'll define our monad by extending the OCaml
module Monad = struct type 'a t = 'a option let return x = Some x let bind = Option.bind let ( let* ) = bind end
The syntax introduced by Ocaml is the
let* which we can define ourselves. (There's also
let+ if we want to use that.)
We can now rewrite the previous example with it:
open Monad let print_res res = match res with | None -> print_endline "none" | Some x -> Format.printf "some: %d\n" x let () = (* example chaining Option.bind, similar to the previous example *) let res : int t = bind (Some 5) (fun a -> bind (Some 6) (fun b -> Some (a + b))) in print_res res; (* same example but using the let* syntax now *) let res = let* a = Some 5 in let* b = Some 6 in Some (a + b) in print_res res
Or I guess you can use the
return keyword to write something a bit more idiomatic:
let res = let* a = Some 5 in let* b = Some 6 in return (a + b) in print_res res
Even though this is much cleaner, the new syntax should melt your brain if you don't understand exactly what it is doing underneath the surface.
But essentially, this is what monads are. A container (e.g.
option) and a
bind function to chain operations on that container.
Bonus: this is how the jane street's ppx_let syntax works (only defined on
result, a similar type to
option, in their library):
open Base open Result.Let_syntax let print_res res = match res with | Error e -> Stdio.printf "error: %s\n" e | Ok x -> Stdio.printf "ok: %d\n" x let () = (* example chaining Result.bind *) let res = Result.bind (Ok 5) ~f:(fun a -> Result.bind (Error "lol") ~f:(fun b -> Ok (a + b))) in print_res res; (* same example using the ppx_let syntax *) let res = let%bind a = Ok 5 in let%bind b = Error "lol" in Ok (a + b) in print_res res
You will need to following
dune file if you want to run it (assuming your file is called
(executable (name monads) (modules monads) (libraries base stdio) (preprocess (pps ppx_let)))
And run it with
dune exec ./monads.exe
leave a comment...