So You Want to Ocaml
Introduction⌗
So, you might have heard that there’s a language called OCaml, and that it is one of the affordable ways of getting into Functional Programming (the other one being F#, but that is a topic for another day).
Pros of OCaml⌗
OCaml is a really good language. There’s a saying that being copied is one of the best compliments that artist can get. Apparently for the programming language it is the same. Or not?
OCaml is imitated by many other languages constantly:
- F#
- Rust
- Haskell (to some extent)
- ReScript
OCaml is one of the ML-family languages (with ALGOL and Lisp being the other ones). OCaml is such a prominent influence on the programming world, even if its influence is not easily observable. The initial version of React was written in OCaml. The ideas of React come from Functional Programming, in part of OCaml, because Jordan Walke was exposed to OCaml in large quantities. If radioactive-ocaml-compiler bites you, you can instantly get better at OCaml and functional programming, or so they say. The initial version of Rust was written in OCaml, before becoming self-hosted. There are many tools and languages that were built in OCaml.
Addressing the elephant…⌗
But if the OCaml is such a nice language, why is it not as popular as Rust, for example? And that is a valid question. The answer is not as simple. There are many myths surrounding the OCaml, while some gripes with the language are true. Sometimes things that people say about the language are blatantly wrong, but sometimes the defense of the language sounds like a high-concentration copium. People believe that the functional programming is “useless” in a sense that no real software can be written in it, other than compilers, and that OCaml’s niche is relegated to that of hobby pet-projects. When you say that it is not the case, you are asked to show the examples of the working software that is popular and widespread (other than Jane Street). At that moment you say that not many people code in OCaml, and as such, we don’t have much to show, compared to the same Python. Less people code in OCaml, less projects in OCaml, less recognition of OCaml, and as such even less people learn about OCaml. A vicious cycle. Sure, it sounds like a copium.
But then again, there are genuine problems of the language that are not addressed by the mainstream user of the OCaml. OCaml tends to attract people who are curious and up for a challenge. As such, being able to run OCaml is a part of initiation ceremony. A test, so to speak. At least that’s how it may be regarded by others, and maybe that’s why there’s nothing been done about it. As such, they tend to detract average people who are curious, but don’t want to spend their precious weekend to figure out what the hell is going on. On top of being able to successfully setup the environment, one must learn about functional programming. This is a task for several weekends. Ain’t no way average people are going to put up with all of the bullying.
The reason Rust doesn’t fail as much, is because Rust is more friendly. Syntax is familiar to C/C++ developers. Rust allows you to write shitty mutable imperative code if you want to. Of course there’s borrow checker, but for simple introductionary examples it works. And that is the most important moment to hook people up. Setting up Rust is super easy. One command and it works. One compiler. Familiar executables as an output. Support for IDE and LSPs. Cargo is also fine. There’s no ambiguity on what to use and what to do. You have an idea and you just think about how to describe it in code. With OCaml it is different.
I must confess… It took me a year to get from reading my first book on OCaml to actually starting to write code. I don’t know why it must have been this way, but it was a running joke that I couldn’t figure out how to run the tools properly. I read books trying to figure it out. But I never found out. Maybe I read books wrongly. But then again, I never had the same problem with Rust. The only way when I was able to make my first program in OCaml for the first time is with the help of ChatGPT. I asked it how to build OCaml program and it gave me some options. Must I say that it doesn’t know nearly as much about OCaml and after some time the answers were useless. But at least I was able to finally code. After that I started to do something I really didn’t want to do: reading the docs. Not only that, I started reading other people’s code in OCaml to see what I was doing wrong. Maybe some people get off of it, but I don’t. Again, why can’t the answer be on the surface as with many Python questions on StackOverflow? Man, I hate Python…
Anyhow, the onboarding process of OCaml is a real problem, and it is a problem that I am going to explain how to solve today. My answer is FAR-FAR-FAR from perfect. But it is a start. It is a necessary building step that hopefully sets you off on your OCaml journey so that you can discover things by yourself.
Installing and Running OCaml⌗
First, we need to install opam
.
Opam is a package manager for OCaml, just like pip
for Python, but a bit more involved.
Opam is nice, because it can do so many things for you, like setting up specific version of OCaml.
But because it wants to do so much, it tries to do as much and if you don’t invoke correct way, you are screwed.
And don’t bother building opam from sources, because you’re probably going to be missing some dependencies.
It’s simpler to install it using package manager.
In case of Fedora:
sudo dnf install opam
It is going to install some dependencies, like OCaml, but opam is going to do a lot of its own things, so it doesn’t matter.
Now, you installed opam, you think it works?
Try installing ocaml-lsp-server
:
opam install ocaml-lsp-server
At this point it should show you some message like “You must run this command!”. Run this:
opam init
This will show you dialogues.
Do as you want, but I decided to change .bash_profile
, so I said “yes”.
After that you think you’re done? Wrong!
Run this:
eval $(opam config env)
I don’t know why, but you won’t be able to find executables downloaded by opam, like dune
, without this command.
Now, we are coming to our crucial part: how do you invoke compiler?
I mean there are several executables:
- ocaml
- ocamlc
- ocamlopt
- ocamlfind
Well, my dear, you usually don’t.
You are supposed to use a build system.
Man, I hate it when the building process is so involved, when all I want is just an executable.
Where are my lovely python script.py
and gcc main.c
?
The way you are going to build things depends on how big your project is and whether you want sane but heavy support of an IDE and LSP or if some vim with basic highlighting and compiling from another terminal window is enough.
If you just want to write simple scripts and don’t have much dependencies, you can use ocamlbuild
.
I personally like ocamlbuild
, because it is very smart: it has internal solver that decides what files to compile, by looking at whether the code is used or not.
Consider it a make
but on steroids, no need to write Makefile
yourself.
There are two ways to invoke ocamlbuild
ocamlbuild main.native # first way
ocamlbuild main.byte # second way
What is the differnce you ask? Well, you see, OCaml can build native binaries like C and C++ do. Binaries that work on your CPU. But then there’s also an OCaml VM. OCaml can generate the bytecode for that VM. Why, do you ask? Well, the bytecode version is indeed slower, but:
- It is smaller in size
- It is portable, like Java
- You can do proper debugging, unlike with native version
If you need to add some dependencies to your program, you can do so with ocamlbuild, but I forgot how. Please google a solution for specific lib, I believe there must be one.
So, another way to build things is dune
.
Many people use dune
, because it plays nicely with LSP and IDE.
If you have an external dependency, dune manages that for you, and doesn’t confuse the LSP with Unbound value
errors.
If it weren’t for that, I would be using ocamlbuild on a daily basis.
Also, if you believe that dune’s documentation sucks, you’re not alone. To initiate a project:
dune init proj project_name
This creates the following project:
project_name/
├── dune-project
├── test
│ ├── dune
│ └── project_name.ml
├── lib
│ └── dune
├── bin
│ ├── dune
│ └── main.ml
└── project_name.opam
test
is where the tests go, bin
is where you put your main.ml
, and lib
is where your main code goes.
main.ml
is essentially used to call the code from the lib
.
The main configuration goes into dune
files:
(executable ...)
or
(library ...)
There are name
and public_name
parameters that are pertinent to library
configuration. Not sure which one is used for finding the library, so I keep them the same.
When writing a configuration for executable
, you need to tell dune to use your library that you are working on.
(exectuable
(public_name project_name)
(name main)
(libraries project_name))
That’s the most important part about configuring dune. After that you can call dune:
dune build
And execute your binary:
dune exec bin/main.exe
At least that’s how I do it.
Now, for the LSP and stuff. So far I was able to setup VSCodium only. Download the binary from the Github, make sure it is not from flatpak. Flatpak tends to isolate packages. Because of that VSCodium won’t find your LSP. Or OCaml and dune for that matter.
Go to extensions and download OCaml Platform
.
After installing make sure that the ocaml-lsp-server
is installed.
VSCodium may also ask you to install ocamlformat
.
Be a sport.
After that you should be able to have a working environment.
And if sometimes LSP doesn’t work as expected, try restarting the server (Ctrl+P -> “OCaml restart”), doing dune build
, and making sure your *.mli
files didn’t hide the important functions or types.
That’s it for installing and setting up. As to how to work with OCaml, that is a topic for another day.