Ocaml Project Setup Guide with Dune

Ocaml Project Setup Guide with Dune

Bootstrapping a project in OCaml can be an exciting experience for beginners. It will help you get up to speed with learning and exploring OCaml. This article will explore the most popular way to set up a toy project in OCaml. I have created a small project here to understand this process. You can check it out here.

Setting Up Your Development Environment

Follow this guide for installing and initializing opam.

  • Install opam, which is OCaml's package manager.
  • Activate opam switch to create a separate development environment for each project and run multiple projects with different OCaml versions. This is similar to Python virtual environments.
  • Install dune using opam. It is the popular build system for OCaml.

I have listed the steps below.

shell-session

$ bash -c "sh <(curl -fsSL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh)"

$ eval $(opam env)

# Navigate to your project directory and run the below command for creating switch with particular ocaml version 
$ opam switch create . 4.08.1 --deps-only

$ opam install dune

Initialize the Project with Dune

shell-session

$ dune init proj first_ocaml_proj

It will generate the following project structure. 

first_ocaml_proj/
    ├── _build/
    ├── bin/
    │   └── dune
    │   └── main.ml
    ├── first_ocaml_proj.opam
    ├── dune-project
    ├── lib/
    │   └── dune
    ├── test/
        └── dune
        └── first_ocaml_proj.ml

Let's understand this structure.

  • _build: Build files go here when you create the project build. 
  • bin: This directory serves as the main entry point for your project. You can rename it, but make sure to adjust the path when building or running the project. I have renamed this dir name to src.
  • bin/dune: Every working directory should have a file to store information, such as the directory name, and dependent libraries.
  • bin/main.ml: You can rename this file to anything and use it as the starting point of your project. Remember to name each file with a .ml extension, which signifies an OCaml file and will be used during build time.
  • dune-project: This file will have metadata about your project, such as dune version, package name, library dependency, etc.
  • first_ocaml_proj.opam: This file will be autogenerated whenever you build your project. It has opam version, project metadata, and dependencies. It is kind of similar to package.json in Nodejs project. 
  • lib: This dir should have files with your business logic.

Commands to build and run your dune project.

shell-session

# .bin/main.exe should be the path of the main file in your project.
$ dune build ./bin/main.exe

# To run the above build, use this command.
$ dune exec ./bin/main.exe

# If you want to run the build directly, build will be stored at _build/default/{path to main file}
$ _build/default/bin/main.exe

Let's examine some specific details in a few of the files mentioned above.

Dune file

(executable
 (public_name first_ocaml_proj)
 (name main)
 (libraries first_ocaml_proj))
  • name - This signifies the name of the current package. This name is used while importing the current package to another package in libraries stanza. 
  • libraries - Add all the external and internal (from within your project) libraries here.

Consider this example, there is a utils file in lib package, and you want to use that in bin/main.ml file. Then you can import it like this: 

# In lib/dune
(name lib)

# In bin/dune
(libraries lib)

# In bin/main.ml. `to_lowercase` is the function in lib/utils.ml
Lib.to_lowercase

Interface files

In OCaml, it is recommended to separate interface files from implementation files, similar to Java projects. Interface files should have the same name as implementation files and end with .mli extension. You should define the signature of functions and custom types you wish to expose in interface files.

An example of an interface file

ocaml

(* type definition *)
type shape = Circle of float | Rectangle of int * int | Square of int

(* function signature *)
val calculate_area : shape -> float

I recommend you check out this small project (Updated code on 22nd Oct 2023) to understand better the concepts described above. If you have any feedback or improvements to the project on Github, please share feedback via pull request.

0 Comments

Leave a reply