Originally published on Medium
I have a confession to make: I love OCaml. It was love at nth sight, with n is a sufficiently small positive integer (kept putting it off in favor of Elixir and Haskell, but I have come to regret it).
OCaml is statically- and strongly-typed, (mostly) functional, fast, safe, and compiled to native, resulting in a single executable binary. The compiler is blazingly fast, and its Hindley-Milner type system (same as for Haskell) is so sophisticated that you don’t need to over-annotate your program and still be type-safe. I’ve always been looking for a fast compiled-to-native—preferably functional—language and OCaml seems to fit the bill so well.
So here I am now, trying to share the love with you. But first, why OCaml?
One trend that has seem to arise on my Twitter feed is ReasonML. Reason is a syntax and toolchain for OCaml from the folks at Facebook. To oversimplify, it’s a new face to OCaml; along with the sister project BuckleScript, we can write OCaml with a JS-ish syntax that is type-safe and can target JS and native (even mobile!). It is evident that it’s gaining traction on Twitter sphere and other online platforms. Even Dan said so:
As with the rise of any technology, new tutorials, articles, and videos start to sprout here and there. I’d also like to contribute, but I thought it might be interesting to cover from the OCaml side of things! It is a bit unfortunate that even though OCaml is not a new language, the materials surrounding it is a bit sparse. I’d like to improve that. (It is steadily improving, and Reason without a doubt has a significant contribution there for bringing more people to the ecosystem.)
The goal of this article is to get you up and running with an installation of OCaml on your computer to actually try and explore stuff. I’m not going to cover syntaxes and such, only about setting up the development environment. Hopefully this can help fellow beginners who are interested in OCaml. I also will provide you some links at the end to follow-up should you want to explore more.
Some disclaimers: you’re not going to get much of Reason here, but I believe the information in this article can also benefit you if you wish to dig deeper. Also, it’s necessary to inform you that Windows story is a bit under represented in OCaml, and AFAIK some parts are broken, so I have to assume that you’re running on either Linux, MacOS, or a VM.
Well then, let’s get to it!
Fun stuff: to install OCaml, you don’t need to install OCaml! (Well, at least not right away.)
What you need first is opam, the OCaml package manager. If you come from other languages, opam is pretty much rustup + cargo (Rust); nvm + npm (Node.js); rbenv/rvm + gem (Ruby); stack (Haskell); pyenv + pip (Python); and others alike. Basically, it is both OCaml version manager and package manager.
I’m going to walk you through installing the beta version of opam v2 (2.0.0~beta5 at the point of this writing). The current official stable version is v1.2.2, but I’ve been using v2 beta for months and feel that it’s stable enough for daily use.
To install, open up your terminal and run:
$ wget https://raw.github.com/ocaml/opam/master/shell/install.sh $ chmod +x install.sh $ ./install.sh --fresh
Here we do three steps:
- Download the install script from the repo
- Make the script executable
- Run the script (you may inspect the content of
install.shfirst before running it).
You can also use the one-liner:
wget https://raw.github.com/ocaml/opam/master/shell/install.sh -O - | sh -s -- --fresh. Note that in general running a shell script from the internet should be done with caution, therefore I advise to inspect the content first.
The script will download the pre-compiled opam binary (of around 5 MB in size) from the GitHub releases page that matches your architecture. It currently has binaries for Linux (i686, arm64, armhf), OpenBSD (amd64), and MacOS/OSX (x86_64). If you already have opam installed, it will make a backup of both your old binary and
.opam directory, so it’s safe!
After downloading, it will ask you where to put the binary. Unless you have a specific reason not to, the default
/usr/local/bin works fine. Make sure that whatever the destination directory is, it is available on your $PATH.
Verify the installation by running
$ opam --version 2.0.0~beta5
Great! We have opam up and running. You can now delete
install.sh if you want since we don’t need it anymore.
Next up, we are going to initialize our opam environment. Run this:
$ opam init
- Check installed and available version controls on your computer (e.g. git, darcs, mercurial)
- Fetch opam repository information (basically where OCaml third party packages are listed)
- Prompt you to add an entry to your
.*rcfile to setup opam environment. The default is no (n), but you will most likely want to say yes (y). This will save you from having to execute some extra commands everytime you open a terminal.
- Install OCaml for you!
At the time of this writing, the latest stable OCaml version is 4.06.0, so that’s what I get on step 4. On installation, this might take some really long time depending on your computer’s spec (particularly on the commands
make world and
make world.opt), and this is fine. That’s because we’re compiling the OCaml binary from source. It might be best to get used to this since it will happen occasionally, e.g. when we want to switch OCaml versions or when we want to start hacking on a recently created/cloned project.
(Note: opam related files are isolated inside the
~/.opam directory, so if you want to uninstall it’s as simple as removing that directory, removing the entry added at your
.*rc file from step 3 above, and removing the opam binary itself.)
Afterwards, you can verify the OCaml installation with
$ ocaml --version The OCaml toplevel, version 4.06.0
Neat! We can now try the builtin REPL, officially called “toplevel”, by invoking
$ ocaml OCaml version 4.06.0 #
# sign is a prompt. We can use it for a simple calculator like such (note: the double semi
;; is used to mark the end of an expression):
$ ocaml OCaml version 4.06.0 # 1 + 1;; - : int = 2 # 2 + 10 * 3;; - : int = 32 # "hello " ^ "world!" - : string = "hello world!"
Congrats! Your OCaml installation is working properly.
One action that you will perform every now and then is switching OCaml versions. As I have described above, opam is also a version manager; that means you can have multiple OCaml compiler versions on your computer and the ability to switch between them at will.
You are also able to have version aliases, say you want to have two aliases
project-b, each using the same OCaml version. The two is considered two different “switch”-es, completely isolated from one another. This is particularly great for dependency isolations — OCaml expects packages to be available globally, and you can imagine the version-conflict troubles it would cause if we use a global namespace for all of our projects’ dependencies. (There are also a neat new feature on opam v2 called local switches, but we’re not going there in this article.)
Now that you have the latest 4.06.0 installed, let’s switch to a slightly older version. A good use case of this is that 4.06.0 was just recently released and introduced a (necessary) breaking change, which currently causes many libraries failing to build (more on that here). So, let’s switch to the older stable version: 4.05.0! Run this command:
$ opam switch create 4.05.0
This will again download the source of the 4.05.0 compiler and build it locally.
(Yes, it will take some time. Better go get your coffee!)
After it’s done, it will advertise a command that you would need to run. Let’s run it now:
$ eval $(opam env)
This will set the necessary environment variables to correctly point to the newly installed switch. To list the installed switches, we can run
$ opam switch # switch compiler description -> 4.05.0 ocaml-base-compiler.4.05.0 4.05.0 default ocaml-base-compiler.4.06.0 default
You can see that we are now using the 4.05.0 switch. You may also notice that the 4.06.0 one has
default as its name. You can switch back to it easily:
$ opam switch default # Run eval $(opam env) to update the current shell environment $ eval $(opam env) $ opam switch # switch compiler description 4.05.0 ocaml-base-compiler.4.05.0 4.05.0 -> default ocaml-base-compiler.4.06.0 default
Nice! Switching between, uh, switches, are so fast. You can create switch aliases with
opam switch create <name> <compiler>, for example
opam switch create project-a 4.05.0, which will create a
project-a switch using 4.05.0 as the base compiler. Note that this command will again rebuild the compiler locally, even when you already have a 4.05.0 switch installed!
(If you’re using ReasonML and/or BuckleScript, this is where you can use
4.02.3+buckle-master as the compiler to get the compatible version.)
Okay! Now we’re going to try to write a simple program, compiling it to a native executable, and running it. Make sure to switch back to 4.05.0 for the purpose of this experiment (
opam switch 4.05.0 && eval $(opam env)).
Let’s come up with your everyday simple program that prints “Hello, OCaml!” to the standard output. Open up your favorite text editor and write this:
let () = print_endline "Hello, OCaml!"
Save it as
hello.ml. We have the source code, now let’s compile it:
$ ocamlopt hello.ml -o hello
This will compile
hello.ml into a native executable named
hello. Let’s now try to run it!
$ ./hello Hello, OCaml!
It works perfectly! How about if we introduce type errors? Let’s try with this program:
let add a b = a + b let c = add 1 "not a number"
Save it as
should_error.ml. What happens when you try to compile it?
$ ocamlopt should_error.ml -o should_error This expression has type string but an expression was expected of type int
In OCaml, you cannot “+” an integer and a string, because the
+ operator only operates on integers, so that resulted in a compile error! Thankfully no weird results such as
1not a number or
You can play around with the file to write more OCaml code, compile, and run it.
Note, however, that you will seldom use
ocamlopt directly to compile programs that have multiple files and complex directory structures that uses third party packages, because you would need to enumerate and wire all the files and packages that your program depend on. Fortunately, there are several tools out there that simplifies this for us, and the majority of the community is converging to Jane Street’s jbuilder as the de facto build system.
I will not delve into the topic of build systems further, perhaps that’s for another article on another time. Just remember that you’re in good hands! :)
The last thing I want to share with you in this article is how we install third party libraries and programs via opam. Let’s differentiate the two:
- Libraries are packages that are meant to be used programmatically in code; while
- Programs are packages that provide executables that you can run, e.g. via command-line.
A package can act as both a program and library, that is, they provide a command-line executable and an API to use it programmatically. Note that “program” and “library” are terms that I come up with myself for explanation purposes, I‘m not sure if there’s a convention for that already.
In the perspective of opam, there are no significant differences between the two; only that programs usually have extra steps after downloading the source, which is to copy the resulting executable binary. Installing packages for both programs and libraries are done using the
opam install command.
Let’s try to install a program,
ocp-indent. It is a utility program that is used to format your OCaml source code to make sure it have proper indentation format (very useful!). Run this command:
$ opam install ocp-indent The following actions will be performed: - install result 1.2 [required by cmdliner] ...snip 8<... ===== 9 to install ===== Do you want to continue ? [Y/n]
It will list the packages to install. Notice that we will install 9 of them, because
ocp-indent depends on 8 other packages for it to work — and opam resolved them automatically for us! Answer
Y (or just enter) to make it proceed with the installation.
First, opam will download the sources (subsequent installs will get them from local cache). Then, opam will build all the packages. If you notice, each of the packages may use different command for building, e.g.
jbuilder build, etc. That is because each package provides its own build instructions. That way library authors can have flexibility on what build systems they use.
After the process is done, you will have the
ocp-indent program installed. Let’s try it!
$ ocp-indent --version 1.6.1 $ ocp-indent hello.ml let () = print_endline "Hello, OCaml!"
Great! You can try for example adding more spaces before
print_endline and run
ocp-indent again on
hello.ml, and it should print out the program with correct indentation. To actually modify the file, you can provide the
-i flag (or
--inplace) , e.g.
ocp-indent hello.ml -i.
So that’s a program! Now, let’s try to install a library package. We’re going to install
alcotest, a lightweight testing framework.
$ opam install alcotest The following actions will be performed: - install astring 0.8.3 [required by alcotest] ...snip 8<... ===== 5 to install ===== Do you want to continue ? [Y/n]
This time it’s going to install 5 packages. Let’s proceed with
Y. It will again download and build the sources.
After it’s done, how about we try it?
$ alcotest zsh: command not found: alcotest
Uh-oh, what happened? Turns out that
alcotest is a library, and therefore it doesn’t provide any command-line executables. Alcotest is meant to be used from code. We’re not going to talk about how to use it now, of course; I just want to give you a look on how programs and libraries differ.
Okay, so how to list the packages that we have installed? We can use
opam list. Try it now! It will list all packages that is installed. You can also use
opam list --installed-root to see the packages you directly installed, cutting out the transitive dependencies.
Package installations are local to the current switch. If you switch to another compiler switch and do
opam list, you will see that the installed packages on the previous switch is gone! Again, because packages are expected to be installed “globally”, this is done so that each switch can have isolation on their installed packages, and projects won’t need to worry about version conflicts.
I hope that you get the gist of installation of packages with opam now! That also concludes my introduction tour to the OCaml ecosystem.
Are your feet wet enough?
In this article we have installed opam, installed an OCaml compiler, learned how to switch compiler versions, how to compile and run OCaml programs, and how to install third party packages. Where to go from here?
For starters, you can explore the official Tutorials page. I recommend skimming the Basics, reading the Structure of OCaml Programs, and go through the rest in sequential fashion on your own pace. It will give you a good view of the capabilities of the language.
I am planning to write more articles on OCaml. Two that I have jotted down on my notes are, in no particular order: setting up your text editor environment for OCaml development, and also building and publishing OCaml packages with jbuilder and topkg. I’m not promising anything yet, though! :D
I have also written an article about building a lightweight Docker image using multi-stage builds with OCaml that is available here: Lightweight OCaml Docker Images with Multi-Stage Builds. When writing it I was using opam v1.2.2, but with the knowledge from this article it’s only a matter of translating the opam commands to the ones for v2 (the only difference is on switching compilers, which I have shown above).
Blocked? Have questions? A few channels of communication you can try: discuss.ocaml.org official Discourse forum, the ReasonML Discord, /r/ocaml on Reddit, and #ocaml on Freenode. The community is full of friendly folks more than willing to help you out!
So that’s it for today, folks! Thanks for reading, I hope you get something out of this post! Tell me in the comments if I can improve anything.