An overview of OCaml

thoughts about software and science

Simon Grondin

30 min read

A quick overview of the OCaml programming language and ecosystem. This is not a tutorial.

This article is targeted at programmers that already know mainstream languages such as Java and JavaScript at an intermediary level.

I’ll cover the basics of the OCaml language with examples and some theory sprinkled here and there. The goal is to give you a “feel” of the language.

If you’ve ever wondered

  • What functional programming looks like in practice
  • How functional languages represent objects
  • How they do asynchronous operations
  • How they even write real world code when everything is immutable

Then you’re about to get answers to all those questions and a whole lot more.

First of all, a quick description. OCaml is a functional programming language. It’s pragmatic: it aims for beautiful, expressive, immutable high level code, but recognizes that sometimes it’s necessary to drop down to imperative code in hot sections. Its syntax can appear radical at first, but it suits the language well. It’s a high level language that retains support for low level operations and excellent abilities to call C code transparently when needed. OCaml uses strong static typing.

Some of the reasons I like it so much are that it lets me write code at a very high or low level depending on the situation, lets me code without having to worry about race conditions, null pointer exceptions, type errors, and finally lets me be confident about the quality of my code. To understand how all those things are possible at once, we need to learn the language, so lets get started.

In the examples I’ll be using Core (standard library) and Lwt (IO/async library).

Let’s get started with some primitive types.

(* This is a comment *)

(* This is an integer: *)

(* Underscores in numbers are ignored and can help with readability: *)

(* These are floats (double precision): *)

(* This is a special value, called unit: *)

Unit is similar to null or void in other languages, except that unit is the only value of type unit. Meaning you can’t set a variable of type Integer or Car to unit. In Java and JavaScript, any object can be null and it leads to Null Pointer Exceptions at runtime. Unit is used in OCaml to signify that a function worked via side effects and returned nothing, more on that later!

Let’s put something into a binding. I’m not calling it a variable, because it’s immutable.

let some_number = 42

OCaml works by evaluating expressions into values. Therefore every top-level expression in a file can be evaluated and it’ll return a single value.

(* These are 2 top-level expressions *)
let a = 5

let b = 6

The following is a single expression. Its type is int. In this case, some_sum will always be 11. The some_sum expression was collapsed to the value 11.

let some_sum =
  let a = 5 in
  let b = 6 in
  a + b

This is another single expression. Its type is int -> int, because it’s a function that takes an int and returns an int.

let my_function a =
  let b = 6 in
  a + b

(* This is a lambda, very similar to JavaScript lambas. *)

let my_function_2 = fun a ->
  let b = 6 in
  a + b

(* I can execute the functions/lambdas like so: *)

my_function 10
>> 16

my_function_2 10
>> 16

(* myfunction arg1 arg2 argN *)

Here, the operator + is an int -> int -> int function. It takes an int, another ints then returns an int. Since the function name is only made of symbols, it’s considered to be an operator and will be called “infix”, meaning it goes between the first and second arguments.

I’m using >> to mark the returned value, it’s not part of the language.

(* Functions can be partially applied: *)
let add_one = (+) 1

(* That returns an int -> int function *)
add_one 4
>> 5
(* This is a string *)
"a b c"

(* Concatenation *)
let my_string = "a b c" ^ " d e"
>> "a b c d e"

>> 'b' (* Note the single quotes, this is a char, not a string *)

(* OCaml has C-style arrays, with O(1) time access. *)
let my_array = [| 1;2;3;4 |]

>> 4

(* This is a tuple, its type is string * int *)
("some string", 123)

Tuples are useful to pass a few values around together as one value This function takes an int * int * int tuple and returns the sum:

let sum_int_triple (a, b, c) = a + b + c

I used deconstruction to break up the tuple directly in the function definition. Many other types can be deconstructed directly in function definitions and patterns, but let’s get to the heart of the language.

OCaml, like all other ML derivatives, supports ADTs (Algebraic DataTypes) and promotes its pervasive use. There are two different forms: SUM types and PRODUCT types. Product types exist in virtually all languages: in JavaScript they are Objects, in C they are Structs, in Java they are Classes/Objects. In OCaml they’re called Records. Here’s a record definition:

type car = {
  maker: string;
  year: int;
  color: string;

(* Once the type is defined, we can instantiate new values of type car *)
let my_car = {maker="Honda"; year=2006; color="blue"}

(* The type of this function is string -> car *)
let make_2005_honda color = {maker="Honda"; year=2005; color}

I used “field punning”. Because a value named color was in scope, I could write color instead of color=color.

(* Printing its color *)
print_endline my_car.color

(* |> passes the value on the left to the function on the right.
It often makes code more readable.
It's like the pipe used in bash: cat myfile.txt | grep error *)

(* Printing its year *)
my_car.year |> Int.to_string |> print_endline
(* or also *)
print_endline (Int.to_string my_car.year)

SUM types in OCaml are called Variants.

(* Here color is Blue OR Red OR Black. It's a sum type. *)
type color = Blue | Red | Black

(* Here car is a maker AND a year AND a color. It's a product type. *)
type car = {
  maker: string;
  year: int;
  color: color;

let my_car = {maker="Toyota"; year=2010; color=Black}

Pattern matching is the way we operate on Variants. Let’s use it to return a boolean depending on wheter the car’s color is black or not.

let true_if_black some_color =
  match some_color with
  | Black -> true
  | _ -> false

Here I match the color with a list of patterns. The program will refuse to compile if the match isn’t exhaustive. The underscore stands for “anything else”. The patterns are checked in order. A better function would list all the colors so if in the future we add new colors the compiler would ask us to go make this match exhaustive again. This is good, because it forces us to think about all the places where color is being used before making a potentially breaking change.

If we want to immediately match against the last argument of a function we can use this syntax sugar:

let true_if_black = function
  | Black -> true
  | Blue -> false
  | Red -> false

true_if_black Black
>> true

true_if_back Red
>> false

When multiple patterns have the same right-hand side, we can use even more sugar:

let true_if_black = function
  | Black -> true
  | Blue | Red -> false

Variants can contain a single value, like Other here:

type color = Blue | Red | Black | Other of string
let string_of_color = function
  | Blue -> "blue"
  | Red -> "red"
  | Black -> "black"
  | Other str -> str

The type of that function is color -> string.

2 things:

  • – The right hand side of the pattern can be any expression as long as all the patterns return the same type (a string in this case).
  • – In the Other case, we access the string by binding it to str. Variants that contain a value create some sort of “context”. To access the inner value, we “unwrapped” the outer context (Other) in the pattern. This way of seeing things will be useful later.

Now the real fun is about to start.

This is a list. Its type is int list. The ints are inside of the list context.


OCaml lists, like most functional languages, are single-linked with the head first. It’s very common to operate over a whole list and accumulate results in another one. The :: operator prepends to a list, it’s an O(1) operation and it can also be used to deconstruct a list.

In FP vocabulary, “head” is the first element of a list and “tail” is the rest. In a single-element list, head is the element and tail is the empty list.

Let’s write a function to double all the odd elements in a list and print the result. Note the let rec in the function definition because it’s recursive.

let input = [3;7;4;6] in
let rec loop inp out = (* inp is the input because "in" is a reserved word. *)
  match inp with
  | head::tail ->
    (* Do the operation *)
    let res = if head mod 2 = 0 then head else head * 2 in
    (* Call yourself recursively with one less input and one more output *)
    loop tail (res::out)
  | [] ->
    out (* We're done processing the input, just return the output *)
loop input [] (* Run loop on the input *)
  |> List.rev (* The output is reversed because we were prepending *)
  |> List.to_string ~f:Int.to_string (* Here ~f is a named argument *)
  |> print_endline (*Prints 6 14 4 6*)

It’s fairly rare to write our own recursive functions to operate over data structures. Usually there’s a helper function available in some module in the standard library, like in this case. ~f:(fun x -> if x mod 2 = 0 then x else x * 2) [3;7;4;6]
  |> List.to_string ~f:Int.to_string
  |> print_endline (*Prints 6 14 4 6*)

Let’s access an element at an arbitrary place in the list.

let my_list = [1;2;3]
(* We can access elements like so: *)
List.nth my_list 2
>> Some 3

The type of List.nth is ‘a list -> int -> ‘a option.

Let’s dissect that.

  1. It’s a function that takes 2 arguments
  2. The first argument is a list of anything, the Java equivalent would be List T.
  3. The second argument is the index we want to access.
  4. The returned value is in the option context.
  5. Note that the type of that value is ‘a, same as the ‘a in the first argument. That means the return value is the same type as the input, no matter what the input was. If I give that function a string list, I’ll get back a string option. For a float list, a float option.

Here’s what the option type looks like:

type 'a option = None | Some of 'a

To access the inner value in our ‘a option, we need pattern matching.

  |> List.nth 2
  |> function
    | None ->
      print_endline "No element at index 2";
    | Some index -> index
  |> print_int

Without the sugar:

  |> List.nth 2
  |> (fun x ->
      match x with
      | None ->
        let () = print_endline "No element at index 2" in
      | Some index -> index)
  |> print_int

First, List.nth isn’t a commonly used function, because it needs to iterate through the list, it’s an O(n) operation, not O(1).

The whole block of code is a single expression of type unit.

Why unit? Because print_int has type int -> unit.

Reminder: unit is roughly similar to null in other languages, except that unit is only equal to unit and other types can’t contain unit. You can’t set my_car to unit if there is no car present like you would do with null. Instead you’d set it to None (which is part of the option type we just saw). If there was a car, you’d set my_car to (Some car).

The reason the return type of print is unit is because it works by side effect. Printing to the screen isn’t something that is represented in the type system, so by using unit as its type it tells us that it did its job via side effects.

The same thing goes for print_endline, but since I wanted to do something else after printing I put it in a dummy binding.

The Option module is full of useful functions to operate on options. One of the most useful is value:

Option.value ~default:5 (Some 3)
>> 3

Option.value ~default:5 None
>> 5

Option.value_exn (Some 3)
>> 3

Option.value_exn None
>> Exception: "Option.value_exn None".

Just like in Node.JS, files are considered modules. Modules contain types and values (especially functions to operate on those local types). In Node.JS, you’d use export to mark an expression as publicly accessible. In OCaml, all top-level expressions are visible until we add an optional .mli file to restrict and enforce the module’s interface.

There are plenty of useful modules, Int, Float, Result, etc.

Before wrapping up with this introduction, I’d like to present two more useful “contexts” and the >>= operator. Result.t and Lwt.t.

type ('a, 'b) result =
  | Ok of 'a
  | Error of 'b

Result.t is used to represent operations that can fail. Instead of throwing exceptions, we can use Result.t to encode the fact that a function can fail directly into the type system. Then to get the result, one has to pattern match over it and explicitely decide what to do in case of failure. The Result.t module has many common operations available out of the box.

  • A favorite of mine is Result.try_with. Its type is (unit -> ‘a) -> (‘a, exn) Result.t.
  • The type definition might look scary, but it really tells us everything about this function.
  • It has only one argument: an unit -> ‘a function
  • It returns a (‘a, exn) Result.t
  • Remember how result uses 2 generics in its type definition?
  • Here those ‘a and ‘b are ‘a and exn (exception), respectively.

In English, it means that Result.try_with takes a function that accepts only unit (called a thunk function) and returns anything. Result.try_with will execute that thunk and wrap the output inside a Result.t value. Therefore the value is either Ok of ‘a or Error of exn. If our thunk finished correctly, the Ok branch in our pattern matching will contain our value. If it threw an exception, the Error branch will contain that exception. An example:

let thunk = fun () -> 5+6

match Result.try_with thunk with
| Ok res ->
(* Do something with the result here *)
  print_endline (Bool.to_string (res = 11)) (* Prints true *)
| Error e ->
  (* Handle the error here *)

It’s easy to create thunks, just wrap some expression in a function that accepts unit.

Lwt is a library to deal with asynchronicity. In Node.JS, we’d pass a callback. In OCaml, we use the Lwt.t context to represent values in the future. Let’s suppose that we have a function that makes an HTTP call, called make_call. Its type is string -> string Lwt.t.

The string argument is the URL and the string result is the HTTP response body.

make_call ""
>>= fun body ->
   (* Wait until we get a response, then print it *)
  return (print_endline body)

The >>= operator has the type ‘a Lwt.t -> (‘a -> ‘b Lwt.t) -> ‘b Lwt.t.

  • Again, it looks scary, but it makes a lot of sense.
  • Here, ‘a is a string (the body) type and ‘b is unit.
  • The >>= operator receives a string Lwt.t (a body in the future).
  • It then applies a transformation (string -> unit Lwt.t).
  • Finally, it returns unit Lwt.t.

In the example, the transformation is taking the string and printing (unit) it. We had to use the return keyword because print_endline returns unit, but the type definition for the operator demands something inside of the Lwt.t context. The return function just wraps something into Lwt.t.

We could chain multiple operations that happen at indeterminate times and have everything be neat and type checked. There are obviously ways to handle failures that can happen in the Lwt.t context, but it’s beyond the scope of this article.

Result.t also defines the >>= operator. It lets us chain multiple calls that could fail (aka calls that return something in the Result.t context). The final value is either an Ok value that didn’t have any error or the first Error value. It’s very useful to chain input validations that way.

Together, the >>= operator (called the monadic bind) and the return function/keyword form a monad. I call them contexts because to me it makes a lot of sense intuitively. What I like is that OCaml doesn’t force the whole weight of Category Theory on me. The community is very opposed to clever hacks. To understand how a library or a codebase works, I head to the .mli files, they’re full of type definitions and with that knowledge only I’m always able to figure out how to use it and how it works. The community prefers explicit code over, for example, defining a complex operator to represent something that makes sense only with knowledge of Category Theory.

The power of OCaml comes from combining powerful concepts together so their effects compound. When writing code, I usually start by encoding as much logic possible into my types.

For my poker game… a denomination is a Variant from Two to Ace, a color is a Variant (Spade | Heart | Diamond | Club), card is a Record of a denomination with a color, etc etc. I encode the fact that user input will be happening in the future (Lwt), that certain operations like validation and I/O can fail (Result).

It goes on and on like that.

Writing the code then becomes a lot easier because the compiler tells me when I don’t match the types correctly. Refactoring and adding features is also considerably easier when I start by editing the types and then fix the compilation errors until everything is good again.

Hopefully you enjoyed this quick overview.

I intentionally barely scratched modules, ignored functors, polymorphic variants, named and optional arguments, imperative programming features and many other things. They aren’t any harder than the concepts presented here, but I wanted to stick to the basics.

It was meant to give a good idea of what the language has to offer and what it looks like. From here, it should be easy to start experimenting with it. I strongly recommend reading Real World OCaml, available online for free or as a book for a small charge.

This entry was posted in Functional Programming, OCaml.