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: return
and bind
:
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 option
type.
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.
An 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 x
or y
is 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 Option
type:
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 monads.ml
):
(executable
(name monads)
(modules monads)
(libraries base stdio)
(preprocess
(pps ppx_let)))
And run it with dune exec ./monads.exe
Comments
leave a comment...