YOCaml

Installation and setup

We will start by setting up a development environment for using YOCaml in a project. This setup is very similar to what is usually done in OCaml.

Creating a YOCaml project (a site generated by YOCaml) requires a ready-to-use OCaml development environment. In this guide, we will see how to set up a reproducible base for using YOCaml. Before starting, make sure that OCaml is installed, in particular by checking that OPAM is available in your $PATH. (You can refer to the instructions on the official site).

Aside on switches

A common approach is to sandbox your project using switches so as not to share dependencies with your other projects. For this guide, we will assume that you are using a switch dedicated to the tutorial. However, if you are already familiar with OCaml and OPAM, you are, of course, free to organize your dependencies as you see fit.

Create an empty switch for the project

Even though YOCaml is released for all OCaml versions >= 5.1.0, we use version 5.3.x for the tutorials. You can run the following commands in a shell from the directory where you want to create your project:

mkdir my-first-blog
cd my-first-blog
opam update
opam switch create . 5.3.0 -y
eval $(opam env)
opam install dune

We start by creating (mkdir) the directory where we will develop our project, then move into it (cd). We update the OPAM package repositories with opam update. We initialize a switch for version 5.3.0 of OCaml (the -y flag validates all steps automatically). Running opam env sets up the necessary environment variables for using the OCaml toolchain (associated with the switch we just created). Finally, we install Dune, the recommended package manager.

Once these commands are executed, the my-first-blog directory will be associated with a switch, in which all our dependencies will be installed (sandboxed).

Project setup

Now that our switch is set up, we can actually put the project in place. There are several approaches to initialize an OCaml project (for example, by running a series of commands), but the approach we recommend here has the advantage of making the project easily cloneable and will simplify setting up continuous integration (to, for instance, automate the deployment of new versions of your site).

Although it is possible to initialize a project using Dune’s CLI (dune init), we will create the files manually in order to better understand the different aspects of the setup and to have fine-grained control over how the project is organized. However, if you are already familiar with the OCaml ecosystem, you can stick to your usual workflow. Since YOCaml is just a collection of regular OCaml packages, no special ceremony is required.

Project description

The project is described in Dune through the dune-project file. This is a file where we describe a project's metadata and dependencies (organized by packages) using S-expressions. At the root of my-first-blog, let's create the dune-project file:

touch dune-project
dune --version

The second line lets us see which version of Dune is installed in our switch, which will allow us to choose the version of the Dune language we will use for our project.

The Dune language is a fantastic tool that allows you to describe the features offered by Dune while ensuring a form of backward compatibility. As new versions of Dune are released, the tool remains compatible with previous versions of the language, allowing a modern version of Dune to run projects configured for an earlier version. If you are using version x.y.z, you should specify the version as (lang dune x.y).

Now that we know our Dune version, we can fill in the dune-project file:

(lang dune 3.20)
(name blog)
(version dev)
(generate_opam_files)
(executables_implicit_empty_intf)

(source (github username/blog))
(license MIT)
(authors "your name <you@domain.com>")
(maintainers "your name <you@domain.com>")

(package
 (name blog)
 (synopsis "My first blog using YOCaml")
 (description 
   "My first personal blog using YOCaml for 
   fun and profit")
 (depends
  (ocaml (>= 5.3.0))))

The first part of the file outlines how Dune will handle the project — essentially configuring the entire project. The two fields that can be a bit confusing are:

  • (generate_opam_files): This instructs Dune to create the OPAM description files. Dune is just the build system, and OPAM is the package manager. This field tells Dune to regenerate the OPAM files, centralizing the project configuration in our dune-project.

  • (executables_implicit_empty_intf): This automatically creates an empty interface file for executables, allowing the compiler to track unused definitions. In practice, this is recommended, but optional.

The second part of the file specifies additional metadata. The repository (here we used GitHub as the host) allows generating additional fields (like a link to the bug tracker). It also lists the project maintainers and authors, and the license under which the project is distributed. Here, it is MIT, but a wide variety of license identifiers are supported.

The final part concretely describes our package: its name, dependencies, etc. In our case, for now, we only depend on OCaml, with the Dune dependency inferred from the (lang dune ...) field.

Development environment

If you started from a switch dedicated to the project, the sandboxing means there is no pre-configured development environment. The OCaml website provides an excellent introduction to setting up a development environment that details the steps to configure your editor. To centralize dependencies (including development ones), we can edit the package section of dune-project to include our development dependencies:

  (package
   (name blog)
   (synopsis "My first blog using YOCaml")
   (description 
     "My first personal blog using YOCaml for 
     fun and profit")
   (depends
-   (ocaml (>= 5.3.0))))
+   (ocaml (>= 5.3.0))
+
+  ;; Dev setup
+  (utop :with-dev-setup)
+  (ocamlformat :with-dev-setup)
+  (ocp-indent :with-dev-setup)
+  (merlin :with-dev-setup)
+  (ocaml-lsp-server :with-dev-setup)))

From our point of view, this set of packages is sufficient to provide a pleasant OCaml development experience, even if, in this tutorial, we may not use all of them:

  • Utop is a REPL that integrates very well with Dune. Running dune utop at the root of your project will start an interactive loop with your project and its available dependencies.

  • ocamlformat is an OCaml code formatter that formats code according to a configurable style.

  • OCP-indent is an efficient tool that, among other things, calculates the cursor position after a line break by applying OCaml-specific indentation rules.

  • Merlin is a server providing IDE services (notably for Emacs and Vim).

  • OCaml-lsp-server is another server that provides IDE services based on LSP, allowing any editor with an LSP client to offer a pleasant OCaml editing experience.

If your editor is properly configured, our my-first-blog directory should be associated with a switch that has all the ingredients to start using YOCaml!

Having both Merlin and OCaml-lsp-server may seem redundant, but from our point of view, it allows the development environment (associated with the with-dev-setup context) to be compatible with many workflows, enabling both Merlin and LSP users to have a functional setup. Additionally, since OCaml-lsp-server depends on Merlin, the dependencies are shared, and adding Merlin comes at a relatively low cost.

Code formatting

Installing Ocamlformat in a switch is not enough to enable formatting; the tool must be configured. Adding a .ocamlformat file at the root of the project (in the my-first-blog directory) is sufficient:

touch .ocamlformat

However, you can of course configure your formatter in more detail by referring to its documentation.

Installing dependencies

Now that we have extensively described our project's development dependencies, we can install all of them. First, run:

dune build

This will update the OPAM file (generated from our dune-project file). Next, we can ask OPAM to install all our dependencies in our current switch (we reuse the -y flag to automatically confirm all steps):

opam install . --deps-only --with-dev-setup -y

Normally, everything should go smoothly. If not, you can find support through the various communication channels of the OCaml community, which is very friendly and responsive (and don’t hesitate to send us any missing information or requests for corrections/clarifications).
From our point of view, this setup is generic enough to bootstrap a wide variety of OCaml projects.

At this point, we have only set up an OCaml development environment, but YOCaml has not been mentioned yet. Indeed, it is time to actually start using YOCaml.

A first use of YOCaml

Now that we have an environment (sandboxed or not, depending on your preferences) suitable for writing OCaml, we can add YOCaml dependencies. As with any time we want to add dependencies to the project, we need to modify the dune-project file (the section dedicated to describing the package):

  (package
   (name blog)
   (synopsis "My first blog using YOCaml")
   (description 
     "My first personal blog using YOCaml for 
     fun and profit")
   (depends
    (ocaml (>= 5.3.0))
+
+   (yocaml (>= 2.5.0))
+   (yocaml_unix (>= 2.5.0))
 
    ;; Dev setup
    (utop :with-dev-setup)
    (ocamlformat :with-dev-setup)
    (ocp-indent :with-dev-setup)
    (merlin :with-dev-setup)
    (ocaml-lsp-server :with-dev-setup)))

The tutorial assumes that we are using at least version 2.5.0 of YOCaml, hence the version constraint, and since we assume the reader is using a UNIX-like distribution, we install yocaml_unix.

As with any modification to the dune-project file, we need to regenerate the OPAM file(s) and then install our dependencies in the current switch:

dune build
opam install . --deps-only --with-dev-setup -y

The difference between the YOCaml package and the YOCaml_unix package will be explained in detail in the following sections, but we can briefly summarize:

  • yocaml describes the functional core of YOCaml and allows you to define programs.
  • yocaml_unix is a runtime context; it takes a YOCaml program (defined using the yocaml package) and executes it.

In general, the concept of a runtime, here yocaml_unix, comes into play at the very end of the project, once all tasks have been defined. But don’t worry—all of these concepts will be fully detailed in the next guide.

Now, we will actually use YOCaml in a simple example: we will just log "Hello World" to the standard output.

A first binary

As mentioned in the introduction, YOCaml is a collection of packages that does not impose any particular ceremony. You can organize your source code however you like. However, for the following guides, we will assume that the sources of our program are located in the my-first-blog/bin directory:

mkdir bin
touch bin/dune
touch bin/blog.ml

As we mentioned in the dune-project file to generate empty interfaces for executables, it is not necessary to create a bin/blog.mli file. We can now modify the dune file to describe our binary:

(executable 
  (public_name blog)
  (libraries yocaml yocaml_unix))

Now, let's modify our blog.ml file to use YOCaml to print Hello World to the standard output. The goal of this step is to ensure that our environment is set up correctly; in this section, it is not necessary to understand what is happening in for the moment:

let program () = 
  Yocaml.Eff.log ~level:`Info "Hello World, from YOCaml"
  
let () = 
  Yocaml_unix.run ~level:`Debug program

Run the program using the following command:

dune exec bin/blog.exe

It should display Hello World, from YOCaml on the standard output. At this point, we have a functional working environment that can be easily modified to add new dependencies! One final step (particularly optional) would be to add a license file at the root of our project and a README.md file that explains, at minimum, how to clone the project and initialize a local switch using the following command:

opam update
opam switch create . --deps-only --with-dev-setup -y

We can now move on to the next section to explore the fundamental concepts of YOCaml and, finally, build our first blog!