Chris Collins

Lab · Playground

OCaml playground

The OCaml 5 toplevel in your browser — type inference, pattern matching, variants, records, and modules with functors.

Code blocks on this page are editable — change them and press Run.

OCaml is the practical branch of the ML family, the languages Robin Milner’s group built in the 1970s as the meta-language of a theorem prover. It arrived from INRIA in 1996 and is still where people go when they want a strong static type system without writing the types down: the Rust compiler was first written in it, Jane Street runs a trading firm on it, and the Coq/Rocq prover and the Flow and Hack type checkers are built with it.

These blocks run the OCaml 5 toplevel compiled to JavaScript with js_of_ocaml (@live-codes/ocaml), in a Web Worker; the first Run downloads about 5 MB. Each run starts a fresh toplevel. Anything you print appears as usual; a block that prints nothing shows the toplevel’s reply instead — each definition’s type and value, as in utop.

Type inference: no annotations, every type known
let compose f g x = f (g x)
let twice f = compose f f
let add n x = x + n
let shout s = String.uppercase_ascii s ^ "!"

let eleven_more = twice (add 10) 1
let loud = twice shout "hey"
let pairs = List.combine [1; 2; 3] ["one"; "two"; "three"]

Nothing is printed, so you see what the toplevel inferred for each definition. compose gets the most general type there is, ('a → 'b) → ('c → 'a) → 'c → 'b: the 'a`s are type variables, so the same function works on integers and strings with no generics syntax. Add a `let () = print_endline loud and the toplevel’s reply gives way to your output.

Pattern matching on lists
let rec pairs = function
  | a :: (b :: _ as rest) -> (a, b) :: pairs rest
  | _ -> []

let rec run_length = function
  | [] -> []
  | x :: rest ->
    match run_length rest with
    | (y, n) :: tail when y = x -> (x, n + 1) :: tail
    | encoded -> (x, 1) :: encoded

let () =
  pairs [1; 2; 3; 4]
  |> List.iter (fun (a, b) -> Printf.printf "(%d,%d) " a b);
  print_newline ();
  run_length (List.of_seq (String.to_seq "aaabccddddde"))
  |> List.iter (fun (c, n) -> Printf.printf "%c%d " c n);
  print_newline ()
Variants: a small expression language and its evaluator
type expr =
  | Num of float
  | Var of string
  | Add of expr * expr
  | Mul of expr * expr
  | Pow of expr * int

let rec eval env = function
  | Num n -> n
  | Var v -> List.assoc v env
  | Add (a, b) -> eval env a +. eval env b
  | Mul (a, b) -> eval env a *. eval env b
  | Pow (a, n) -> eval env a ** float_of_int n

(* Symbolic differentiation is just more pattern matching *)
let rec deriv x = function
  | Num _ -> Num 0.
  | Var v -> Num (if v = x then 1. else 0.)
  | Add (a, b) -> Add (deriv x a, deriv x b)
  | Mul (a, b) -> Add (Mul (deriv x a, b), Mul (a, deriv x b))
  | Pow (a, n) -> Mul (Mul (Num (float_of_int n), Pow (a, n - 1)), deriv x a)

let rec show = function
  | Num n -> Printf.sprintf "%g" n
  | Var v -> v
  | Add (a, b) -> Printf.sprintf "(%s + %s)" (show a) (show b)
  | Mul (a, b) -> Printf.sprintf "%s·%s" (show a) (show b)
  | Pow (a, n) -> Printf.sprintf "%s^%d" (show a) n

let f = Add (Mul (Num 3., Pow (Var "x", 2)), Mul (Num 2., Var "x"))   (* 3x² + 2x *)
let () =
  Printf.printf "f     = %s\n" (show f);
  Printf.printf "f(2)  = %g\n" (eval [ ("x", 2.) ] f);
  Printf.printf "f'    = %s\n" (show (deriv "x" f));
  Printf.printf "f'(2) = %g\n" (eval [ ("x", 2.) ] (deriv "x" f))

Delete the Pow case from deriv and the OCaml compiler warns that the match is no longer exhaustive, naming the case you forgot — most of the reason people love variants. (This toplevel doesn’t show warnings; run it and you get the Match_failure the warning was about.)

Records, options, and no null
type book = { title : string; author : string; year : int; isbn : string option }

let shelf = [
  { title = "SICP"; author = "Abelson & Sussman"; year = 1985; isbn = Some "0262010771" };
  { title = "The Art of Computer Programming"; author = "Knuth"; year = 1968; isbn = None };
  { title = "Real World OCaml"; author = "Minsky, Madhavapeddy"; year = 2013; isbn = Some "144932391X" };
]

let describe b =
  let isbn = match b.isbn with Some i -> "ISBN " ^ i | None -> "no ISBN" in
  Printf.printf "%-34s %4d  %s\n" b.title b.year isbn

let () = List.iter describe shelf

(* Functional update: a copy with one field changed *)
let second_ed = { (List.hd shelf) with year = 1996 }
let () = describe second_ed

let oldest = List.fold_left (fun acc b -> if b.year < acc.year then b else acc) (List.hd shelf) shelf
let () = print_endline ("oldest: " ^ oldest.author)

let find_isbn t = List.find_opt (fun b -> b.title = t) shelf |> Option.map (fun b -> b.isbn) |> Option.join
let () =
  [ "SICP"; "The Art of Computer Programming"; "Dune" ]
  |> List.iter (fun t -> print_endline (t ^ ": " ^ Option.value (find_isbn t) ~default:"?"))
Modules and functors: a map keyed by anything ordered
(* A module type is an interface; a functor is a function from modules to modules *)
module CaseInsensitive = struct
  type t = string
  let compare a b = String.compare (String.lowercase_ascii a) (String.lowercase_ascii b)
end

module WordCount = Map.Make (CaseInsensitive)

let count text =
  String.split_on_char ' ' text
  |> List.filter (( <> ) "")
  |> List.fold_left
       (fun m w -> WordCount.update w (function None -> Some 1 | Some n -> Some (n + 1)) m)
       WordCount.empty

let () =
  count "The cat and THE hat and the bat"
  |> WordCount.iter (fun w n -> Printf.printf "%s=%d " w n);
  print_newline ()

(* Your own signature, and a module that hides its representation behind it *)
module type STACK = sig
  type 'a t
  val empty : 'a t
  val push : 'a -> 'a t -> 'a t
  val pop : 'a t -> ('a * 'a t) option
end

module Stack : STACK = struct
  type 'a t = 'a list
  let empty = []
  let push x s = x :: s
  let pop = function [] -> None | x :: s -> Some (x, s)
end

let () =
  let s = Stack.(empty |> push 1 |> push 2 |> push 3) in
  match Stack.pop s with
  | Some (top, _) -> Printf.printf "top is %d\n" top
  | None -> print_endline "empty"
Errors as values: Result and the let* binding
let parse_int s =
  match int_of_string_opt (String.trim s) with
  | Some n -> Ok n
  | None -> Error (Printf.sprintf "%S is not a number" s)

let positive n = if n > 0 then Ok n else Error (Printf.sprintf "%d is not positive" n)

let ( let* ) = Result.bind

let ratio a b =
  let* x = parse_int a in
  let* y = parse_int b in
  let* y = positive y in
  Ok (float_of_int x /. float_of_int y)

let () =
  [ ("22", "7"); ("10", "zero"); ("5", "-2") ]
  |> List.iter (fun (a, b) ->
         match ratio a b with
         | Ok r -> Printf.printf "%s/%s = %.4f\n" a b r
         | Error e -> Printf.printf "%s/%s: %s\n" a b e)