Getting Started with OCaml in 2021

OCaml is an awesome language with many fine features. I enjoy using it immensely!

Unfortunately, it suffers from a perceived weakness in how to get started. Like any new skill, there can be a learning curve. The tools are all there, but combining them for a good developer experience might seem difficult at first.

Often I’ve found that the barrier for getting into a new langauge is less about the new features of that language and more about learning the tools to become productive in that language. The package managers, build tools, and editor integration of a new language can be confusing, making for an awful experience.

Perhaps my opinionated guide to getting started with OCaml in 2021 will help reduce any mental blocks against trying out this excellent language.

Install Opam

First it’s necessary to install OCaml and Opam. Opam is the default package manager for OCaml projects. Ignore the other options for now, once you know more about what you want, you can make an informed choice. For now if you speak OPAM, you’ll get the most out of the community.

On Linux, use your local package manger, e.g., apt-get install opam for Debian and apt install opam for Ubuntu. For MacOS, use homebrew brew install opam. I’ll assume if you run something else, you can handle looking up how to install things.

On my Mac I get Opam 2.1.0:

$ opam --version
2.1.0

Once you’ve got Opam installed, you should be able to move on to the next step.

Choose an OCaml Version

I strongly recommended that you pick a single OCaml version that your project will compile against. Supporting multiple compiler versions is possible and usually not too diffcult, but it complicates the process right now.

Running opam switch list-available will show you a long list of every possible OCaml compiler. Choose the latest mainline compiler identifed by Official release X.XX.X where currently the latest is 4.13.0. Ignore the others.

opam switch list-available
...
ocaml-variants                         4.12.0+domains                         OCaml 4.12.0, with support for multicore domains
ocaml-variants                         4.12.0+domains+effects                 OCaml 4.12.0, with support for multicore domains and effects
ocaml-variants                         4.12.0+options                         Official release of OCaml 4.12.0
ocaml-base-compiler                    4.12.1                                 Official release 4.12.1
ocaml-variants                         4.12.1+options                         Official release of OCaml 4.12.1
ocaml-variants                         4.12.2+trunk                           Latest 4.12 development
ocaml-base-compiler                    4.13.0~alpha1                          First alpha release of OCaml 4.13.0
ocaml-variants                         4.13.0~alpha1+options                  First alpha release of OCaml 4.13.0
ocaml-base-compiler                    4.13.0~alpha2                          Second alpha release of OCaml 4.13.0
ocaml-variants                         4.13.0~alpha2+options                  Second alpha release of OCaml 4.13.0
ocaml-base-compiler                    4.13.0~beta1                           First beta release of OCaml 4.13.0
ocaml-variants                         4.13.0~beta1+options                   First beta release of OCaml 4.13.0
ocaml-base-compiler                    4.13.0~rc1                             First release candidate of OCaml 4.13.0
ocaml-variants                         4.13.0~rc1+options                     First release candidate of OCaml 4.13.0
ocaml-base-compiler                    4.13.0~rc2                             Second release candidate of OCaml 4.13.0
ocaml-variants                         4.13.0~rc2+options                     Second release candidate of OCaml 4.13.0
ocaml-base-compiler                    4.13.0                                 Official release 4.13.0
ocaml-variants                         4.13.0+options                         Official release of OCaml 4.13.0
ocaml-variants                         4.13.1+trunk                           Latest 4.13 developmet
ocaml-variants                         4.14.0+trunk                           Current trunk
...

At this point, install the latest OCaml 4.13.0:

$ opam switch create 4.13.0

<><> Installing new switch packages <><><><><><><><><><><><><><><><><><><><>  🐫
Switch invariant: ["ocaml-base-compiler" {= "4.13.0"} | "ocaml-system" {= "4.13.0"}]

<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><>  🐫
∗ installed base-bigarray.base
∗ installed base-threads.base
∗ installed base-unix.base
∗ installed ocaml-options-vanilla.1
⬇ retrieved ocaml-base-compiler.4.13.0  (https://opam.ocaml.org/cache)
∗ installed ocaml-base-compiler.4.13.0
∗ installed ocaml-config.2
∗ installed ocaml.4.13.0
Done.

You can start using this version by typing the following:

$ opam switch set 4.13.0

And verify which switch you are using:

$ opam switch show
4.13.0

When you work with several OCaml projects, it’s best to create a switch per project, as it keeps each project isolated and prevents issues with installing conflicting versions of libraries. For example, I use a naming scheme of ocaml-version-project-name, e.g., 4.13.0-ocurrent. Then in each project directory, run opam switch link 4.13.0-ocurrent to setup that named switch for that specific directory. Opam will take care of setting that switch in your shell when you change into that directory.

Creating Your Project Directory

For this step we need the Dune build tool, so go ahead and install it with opam install dune. Dune comes with a simple scaffolding command to create an empty project that is really useful to get started.

I’m calling my project box, so run:

$ dune init proj box
Success: initialized project component named box

In the project generated, we get a library component, a CLI, and a test component, which will all compile out of the box.

$ cd box
$ tree
.
├── bin
│   ├── dune
│   └── main.ml
├── box.opam
├── lib
│   └── dune
└── test
    ├── box.ml
    └── dune

3 directories, 6 files

Lets try a compile:

$ dune build @all
Info: Creating file dune-project with this contents:
| (lang dune 2.8)
| (name box)

Running the CLI:

$ dune exec bin/main.exe
Hello, World!

Each of the bin, lib, and test directories contains the source code in the form of *.ml files, along with a dune file which tells Dune how to build the source and on what libraries it depends. The box bin\dune file declares it’s an executable with a name box and depends on the box library.

(executable
 (public_name box)
 (name main)
 (libraries box))

Adding a Dependency

CLI tools require command line parsing, Cmdliner is a common library that implements CLI parsing. We need to add it in two places: first in the dune-project file, to get it installed, and then in bin/dune, to say where we’re using it.

One small digression, when generating our project, dune created an box.opam file. This describes our project to Opam, telling it what libraries it requires and what the project does. You need this if you ever publish a package for other people to use. Newer versions of Dune can generate the box.opam file from a dune-project file. Having a single source of information is helpful, so lets create that file:

(lang dune 2.8)
(name box)

(generate_opam_files true)

(package
 (name box)
 (depends
  (ocaml (>= 4.13.0))
  (cmdliner (>= 0.9.8)))
 (synopsis "Box cli"))

Remove the rm box.opam file to test the generation. Now run dune build @all to regenerate the Opam file. This file should be checked in, and any further edits should be at the top-level dune-project file, which should look like this:

$ cat box.opam
# This file is generated by dune, edit dune-project instead
opam-version: "2.0"
synopsis: "Box cli"
depends: [
  "dune" {>= "2.8"}
  "ocaml" {>= "4.13.0"}
  "cmdliner" {>= "0.9.8"}
  "odoc" {with-doc}
]
build: [
  ["dune" "subst"] {dev}
  [
    "dune"
    "build"
    "-p"
    name
    "-j"
    jobs
    "@install"
    "@runtest" {with-test}
    "@doc" {with-doc}
  ]
]

The final step is to actually install the cmdliner library. Run opam install . --deps-only -ty, which will look at the *.opam files present and install just their dependencies with the correct version bounds. The -y says yes to installing the packages. You can remove it if you like by pressing Y or if you want to review what will be installed. -t will run the package tests, which isn’t always necessary, but it’s sometimes useful for certain packages with native C components.

Alternatively you could run opam install cmdliner, as this doesn’t look at version constraints in *.opam files, you might not get what you expect.

Editor Tooling

Finally, you’ll want to get comfy with your chosen editor. If you have a preference, you should use the native LSP support in that editor, along with installing opam install ocaml-lsp-server. OCaml is standardising on the LSP protocol for editor interaction. If you have no editor preference, then start with VSCode and install the OCaml LSP package from the Marketplace.

Personally, I’m using Emacs with the LSP mode eglot, which works really nicely, along with some customisations to bind certain LSP actions to keys. I highly recommend getting into Emacs as an editor because the customisation via a fully-featured language, like Lisp, is fantastic if you live in your editor like I do.

This post is an update to an earlier post by Adam in 2017, and I hope this short tutorial helps get you started with OCaml!

Copyright © Tim McGilchrist 2007-2024
Powered by Hakyll