Starting an OCaml app project using Dune

On configuring the blazingly-fast, state-of-the-art OCaml build system to get your app running.

Seascape. [source]

Requirements

To be able to follow this tutorial, you would need to have opam, the OCaml package manager, installed. Consult the official docs on how to install it on your machine. I recommend using your OS package manager, but if you’re downloading the binary, don’t forget to also download an external solver beforehand.

$ opam --version
1.2.2

Editor setup

You will also need to setup your editor to handle OCaml files. I highly recommend Microsoft’s VSCode with the vscode-reasonml extension as it works great out of the box (seriously, it rocks!).

Screenshot of example OCaml code in VSCode with vscode-reasonml extension.

Initial setup

Let’s create a new directory for our awesome project! As naming is one of the hardest problems in Computer Science, we will choose to be uninspired and pick the most perfect name for our project: todolist.

$ mkdir todolist
$ cd todolist
$ opam switch todolist --alias-of 4.06.1
$ eval $(opam config env)
$ opam install merlin ocp-indent dune utop
  1. merlin, available via the ocamlmerlin command, is the tool providing OCaml IDE experience that can be integrated to your editor. Its features include context-sensitive auto-completion, error-reporting, querying type information and documentation, and jumping to definition. Merlin will most likely be used by your editor plugins and not you directly.
  2. ocp-indent, a simple, customizable tool to indent your OCaml source code. As with Merlin, most editor plugins also make use of this to indent your code automatically. If you want a more opinionated solution, there is also ocamlformat, but it’s still in active development and might not be stable yet. You might still want to try it, though.
  3. dune, the installable package of Dune. At the point of this writing, Dune’s latest version is 1.0.1.
  4. utop, an improved REPL (toplevel) for OCaml. It is based on lambda-term and supports auto-completion. In general, I favor utop as our REPL instead of the builtin ocaml, since the former have better UX.

Dune basics

In this section, we’re going to create a new Dune project.

Executables

The first concept that we’re going to explore is an executable. An executable is, as the name implies, a program that can be executed. This is contrast to a library, which we will explore in the later section.

Contents of todolist/dune file.
Contents of todolist/main.ml file.
$ dune build main.exe
$ dune exec ./main.exe
Hello, world!
$ mkdir bin
$ mv dune main.ml bin/
$ dune clean
$ dune exec bin/main.exe
Hello, world!

Libraries

Structuring your code in a modular way is one of software engineering best practices. In OCaml and Dune, such structure can be achieved through the use of libraries. While there may be a formal definition of it, I like to think of a library as a collection of modules that can be depended on.

$ mkdir lib
Contents of lib/dune file.
Contents of lib/math.ml file.
Modified contents of bin/dune file.
$ dune build bin/main.exe
Modified contents of bin/main.ml file.
$ dune exec bin/main.exe
5
2

Interface files

One thing you may (or may not) observe from the above steps is that anything you write on math.ml module will be automatically visible from the client module (in this case, main.ml). This is how OCaml works; by default all identifiers are exposed from a module.

Contents of lib/math.mli file.
$ dune exec bin/main.exe
(...some output...)
File "bin/main.ml", line 6, characters 15-23:
Error: Unbound value Math.sub
(...some output...)
File "lib/math.ml", line 3, characters 4-7:
Error (warning 32): unused value sub.
Modified contents of lib/math.mli file.

Trying out libraries interactively via utop

Another benefit of having the libraries in a separate directory from the executables is that you can use utop REPL to play around with your functions. The REPL will evaluate the files given, so if any of it produces a side-effect on evaluation time (e.g. printing, starting a web server) like what typically executables entry point do, it will be run on starting the REPL, which may not be what you want.

A colorful utop session.
utop # open Lib;;utop # #show Math.add;;              (* 1 *)
val add : int -> int -> int
utop # Math.add 1 2;; (* 2 *)
- : int = 3
utop # let add2 = Math.add 2;; (* 3 *)
val add2 : int -> int = <fun>
utop # add2 5;; (* 4 *)
- : int = 7
  1. Querying a type of a function;
  2. Actually invoking a function;
  3. Partially applying a function; and
  4. Invoking the partially-applied function with the remaining arguments.

That’s it for now!

That concludes our exploration on Dune to build our OCaml app project. I have by no means exhausted the capabilities that Dune have, but what I demonstrated in this article is sufficient to get you up and running with your own app.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Bobby Priambodo

Software Engineer. Functional programming and distributed systems enthusiast. Java, JavaScript, Elixir, OCaml, Haskell.