OCaml

OCaml is a member of the ML family, developed at INRIA in France. Caml appeared in the 1980s, and Objective Caml (later OCaml) added objects in 1996. It is functional first, but it also has mutable state, loops and objects, and it compiles to fast native code. Its type system finds many errors before the program runs, while inferring nearly all types for you.

Paradigm: Functional Programming

What Makes OCaml Special

  • Type inference. The compiler works out the type of every function and value from how they are used. You rarely write a type, yet the program is fully statically typed.
  • Variants and pattern matching. A type can list its cases, each with data. match takes values apart, and the compiler warns when a case is missing.
  • Modules and functors. A module groups types and functions behind a signature. A functor is a function from modules to modules, so whole components can be parameterized.
  • Functional first, imperative when useful. Data is immutable by default, but ref cells, arrays, for loops and exceptions are there when they are the simplest tool.
  • Options instead of null. A value that may be missing has type option, and you must handle None before you can use it.
  • Fast and predictable. The native compiler produces efficient code, and evaluation is strict (not lazy), so performance is easier to reason about than in Haskell.
  • Multicore since OCaml 5. Version 5 (2022) added domains for parallelism and effect handlers for concurrency.

Example: Variants, pipelines, partial application and an option

type shape =
  | Circle of float
  | Rect of float * float

let area = function
  | Circle r -> Float.pi *. r *. r
  | Rect (w, h) -> w *. h

let shapes = [ Circle 1.0; Rect (2.0, 3.0) ]

(* a pipeline: each step feeds the next *)
let total = shapes |> List.map area |> List.fold_left ( +. ) 0.0
let () = Printf.printf "%.2f\n" total                (* 9.14 *)

(* partial application: add 5 is a new function *)
let add x y = x + y
let add5 = add 5
let () =
  List.iter (fun n -> Printf.printf "%d " (add5 n)) [ 1; 2; 3 ];
  print_newline ()                                     (* 6 7 8 *)

(* option instead of null *)
let rec find_first p = function
  | [] -> None
  | x :: rest -> if p x then Some x else find_first p rest

let () =
  match find_first (fun n -> n > 2) [ 1; 2; 3; 4 ] with
  | Some n -> Printf.printf "found %d\n" n             (* found 3 *)
  | None -> print_endline "none"

(* imperative style, when it is simplest *)
let counter = ref 0
let () =
  for i = 1 to 3 do counter := !counter + i done;
  Printf.printf "%d\n" !counter                       (* 6 *)

How It Works

  • area is defined with function, which is a match on its argument. Its type, shape -> float, is inferred. Note the dotted operators *. and +. for floats, since OCaml never converts numbers silently.
  • |> passes the value on its left as the last argument to the function on its right, so the pipeline reads in the order it runs.
  • add 5 gives back a function that waits for y. Every OCaml function takes one argument at a time (it is curried).
  • find_first returns Some x or None. The caller must match on both, so a forgotten check is a warning, not a crash.
  • The last block shows that OCaml also allows mutation: ref creates a cell, ! reads it and := writes it.

History and Where It Is Used

OCaml is used for compilers and language tools, theorem provers (Coq, now Rocq, is written in it), static analyzers (Flow and Infer from Facebook), and in finance, notably at Jane Street. It is also common in teaching programming languages. The standard tools are dune (build) and opam (packages). The ML family also includes F#, which brings much of the OCaml style to .NET.

Learn More