Skip to content

OCaml features you may not be familiar with

Here are some OCaml features that weren't discussed in CS 4, but which are very useful in day-to-day programming.

The |> (pipe) operator

The |> (pipe) operator is a reverse-apply operator (analogous to a Unix pipe). It's very convenient when you want to transform some inputs repeatedly by applying one function after another.

Instead of writing this:

func3 (func2 (func1 data))

you can write this:

data |> func1 |> func2 |> func3

Aside from having fewer parentheses, it's often easier to read: "Create data, then apply func1, func2, and func3 to it in succession."

Also, because of OCaml's auto-currying of functions, it even works if one or more of the functions take additional arguments, just as long as the data structure is the last argument to the function. So we might have e.g.:

data |> func1 arg1 |> func2 arg2a arg2b |> func3

instead of:

func3 (func2 arg2a arg2b (func1 arg1 data))

Whether this is clearer is a matter of opinion.

When there are a lot of transformations, it's nice to write them on multiple lines:

data
  |> transform1
  |> transform2
  |> transform3 arg
  |> transform4 arg1 arg2
  (* etc. *)

This is much more readable than the usual notation:

transform4 arg1 arg2 (transform3 arg (transform2 (transform1 data)))

and it's also easier to extend with more transformations.

The @@ (apply) operator

The "cousin" of the |> operator is the @@ (forward apply) operator. Like |>, it's never required, but sometimes it can make code a bit nicer by removing the need for some parentheses.

Note

If you know the Haskell language, you'll recognize the @@ operator as the same thing as the $ operator in Haskell.

The @@ operator is used to apply a function to its argument, so f @@ x is the same as f x. Seems pointless, right? But consider f (1 + 2). With @@ this becomes f @@ 1 + 2 and you've eliminated the need for the parentheses. Similarly:

func3 (func2 (func1 data))

can be written as:

func3 @@ func2 @@ func1 data

and the parenthesis-saving is even greater! (Note that this operator associates to the right.)

A common use for this is when signalling errors. In the course compilers, most errors use the failwith function:

failwith "my error message"

This is fine, but very often, the error message needs to contain formatted data e.g.

if i > max_i then
  failwith (Printf.sprintf "i value: (%d) is too large" i)
else
  ...

This can be rewritten using the @@ operator as:

if i > max_i then
  failwith @@ Printf.sprintf "i value: (%d) is too large" i
else
  ...

and sometimes it's nice to put the Printf.sprintf on its own line:

if i > max_i then
  failwith @@
    Printf.sprintf "i value: (%d) is too large" i
else
  ...

In fact, this case is so common that we've written a helper function called failwithf ("failwith with formatting"). Using that, we could rewrite the above code as:

open Support.Utils

...

if i > max_i then
  failwithf "i value: (%d) is too large" i
else
  ...

Labelled arguments

OCaml has a neat feature called labelled arguments which we didn't cover in CS 4. Just like you'd expect, this feature means that you can label a function argument with a name, and you have to use that name when calling the function. What's nice is that you don't have to put labelled arguments in any particular order; as long as the label is there, the function will know what to do about them.

The syntax is described in detail in the link, but here's an example:

let f ~x ~y = x - y     (* x and y are labelled arguments *)

let _ = f ~x:10 ~y:20   (* you need the labels when calling the function *)
let _ = f ~y:20 ~x:10   (* does the same thing *) 

Why would you use this?

In our experience, the toughest OCaml bugs to track down are in functions that have more than one argument of the same type. It's easy to switch the arguments by mistake, and then you have a very hard-to-find bug.

The solution is to use labelled arguments. This way, when you call the function, you have to specify which argument you mean, which makes errors of this kind far less likely.

We're not suggesting you do this for every function, but it's certainly a trick to keep in mind, and one which we've used in much of the supporting code.

Also, OCaml supports optional arguments, which are like labelled arguments except the function gets a default value you specify if the labelled argument is left off. The link above describes those too.