DEV Community

Cover image for Getting your feet wet with OCaml
Bobby Priambodo
Bobby Priambodo

Posted on • Originally published at Medium

Getting your feet wet with OCaml

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.

After bashing Java for years in college, working with JavaScript for a year after I graduated, and almost another year working with Java (karma?), I can’t help but appreciate languages that have explicit types. The compiler is a very great, smart friend that can actually stop you from stupid things like using wrong types in your code. OCaml’s compiler is even more so. And it’s blazingly fast!

So here I am now, trying to share the love with you. But first, why OCaml?

The rise of ReasonML

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:

Fun pattern I noticed while making this list: people who came to the @reactjs community early are now mostly tweeting about @reasonml.

— Dan Abramov (@dan_abramov ) December 3, 2017

Acknowledgement from Dan!

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!

Installation

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
Enter fullscreen mode Exit fullscreen mode

Here we do three steps:

  1. Download the install script from the repo
  2. Make the script executable
  3. Run the script (you may inspect the content of install.sh first 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:

$ opam --version
2.0.0~beta5
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This will:

  1. Check installed and available version controls on your computer (e.g. git, darcs, mercurial)
  2. Fetch opam repository information (basically where OCaml third party packages are listed)
  3. Prompt you to add an entry to your .bashrc/.zshrc/.*rc file 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.
  4. 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:

$ ocaml --version
The OCaml toplevel, version 4.06.0
Enter fullscreen mode Exit fullscreen mode

Neat! We can now try the builtin REPL, officially called “toplevel”, by invoking ocaml:

$ ocaml
       OCaml version 4.06.0

#
Enter fullscreen mode Exit fullscreen mode

The # 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!"
Enter fullscreen mode Exit fullscreen mode

Congrats! Your OCaml installation is working properly.

Switching OCaml versions

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-a and 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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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:

$ 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.)

Writing, compiling, and running programs

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!"
Enter fullscreen mode Exit fullscreen mode

Save it as hello.ml. We have the source code, now let’s compile it:

$ ocamlopt hello.ml -o hello
Enter fullscreen mode Exit fullscreen mode

This will compile hello.ml into a native executable named hello. Let’s now try to run it!

$ ./hello
Hello, OCaml!
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 NaN.

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! :)

Installing libraries and programs with opam

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:

  1. Libraries are packages that are meant to be used programmatically in code; while
  2. 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]
Enter fullscreen mode Exit fullscreen mode

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. make, 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!"
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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?

So, what’s next?

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.

One canonical resource that most folks recommend is the Real World OCaml book, which is available for free online. OCaml from the Very Beginning is also a recommended book as an entry point.

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.

Top comments (8)

Collapse
 
eljayadobe profile image
Eljay-Adobe

"Also, it’s necessary to inform you that Windows story is a bit under represented in OCaml..."

Microsoft (and Don Syme, at Microsoft Research, Cambridge) has created F#, which is pragmatically OCaml for .NET.

As with C# and VB.NET, F# is a first-class citizen language for .NET/Mono.

Collapse
 
jvanbruegge profile image
Jan van Brügge

I learned ocaml in university, I've read the article and I still dont get why people prefer ocaml over Haskell.
Yes, having a strongly typed language with a good compiler is amazing, but Haskell is that too. But then there are the things that ocaml lacks. Typeclasses for example. They are far simpler than the modules of ocaml and do a much better job. It is just plain annoying that + does not work for floats. Why do i have to use +.? In Haskell it is defined as Num a => a -> a -> a. But ocaml just lacks a way to express that. Then it's functional, but still allows you to to side effects all over the place. This is not the security I expect from a functional language. If I have a function from a to b, I do not want that function to call my database or launch missiles. But then again ocaml lacks a way of expressing the idea of monadic IO. In the end you have modules that you can use to emulate part of it, but it still feels more like duck typing than a thought out system.
Another thing is type signatures. Why do I have to put them in a different file? I want to have them near the function. And no, putting them inline is not an alternative for more complex types.
Then why are tuples defined with a star and not like they are used in the code with a comma? Why double semicolon? Why a tick before type variables? Why does multithreading has to be so much more complicated than in Haskell?
All in all, I just dont see a compelling reason to use ocaml when you could use haskell instead. Feel free to correct me.

Collapse
 
bobbypriambodo profile image
Bobby Priambodo

Hi Jan, thank you for your opinion. It's great that Haskell works for you.

Your critiques are indeed valid. On the typeclasses story, there are works on modular implicits that aim to solve that (your exact concerns are stated on the introduction of the paper).

I see OCaml as a friendlier introduction to FP; if you're uncomfortable with diving deep to FP, there are escape hatches available to use imperative constructs. Indeed that sacrifices the security you get like with Haskell, but then again everything is a trade-off. Reason shows that you can even incorporate a JS-ish syntax for it to fix many of the warts, which I guess is interesting to some demography of developers.

Haskell is a great language, and I'm sure Haskell programs are fast, but the development experience for me is slower than OCaml. The compilation takes a long time, ghc-mod is slow to query types, Intero is better but still nowhere near Merlin in my opinion. Compilation in OCaml is fast, either you compile to native, bytecode, or JS (with BuckleScript or js_of_ocaml).

No straightforward way to annotate type signatures is also one of the bad sides, it prevents type-driven development like what you can do with Haskell. But with how fast Merlin is and how good the type inference is, it doesn't feel that much limiting for me.

But that said, I still think both Haskell and OCaml are on the top of my favorite languages list, so... :)

Collapse
 
theodesp profile image
Theofanis Despoudis

Unfortunately those languages like OCaml, Haskell will only be for destined for a weekend workshop. The closest ones you can get that are in the mainstream are F#, Scala, and Clojure.

Collapse
 
bobbypriambodo profile image
Bobby Priambodo • Edited

Mainstream, probably unlikely. But certainly not only weekend workshop in my opinion. Facebook has a list of tools used that is built on OCaml (flow, infer, pfff, Hack's parser). Docker's DataKit and VPNKit also use OCaml, and their desktop apps for Windows and Macs have some OCaml code too. ReasonML and BuckleScript are gaining traction too on frontend landscape. Finance and trading also seems to attract the use of OCaml, for example at Jane Street and Bloomberg. Ahrefs uses it for crawling the web, processing 6 billion pages a day. I've got this information from this page on OCaml's official site.

For Haskell, among others Facebook also uses it to power their spam detection systems. Pretty sure there are many companies too using Haskell in production (at least there seems to be more Haskell job openings than OCaml from what I can see).

That said, I don't know about Clojure, but indeed F# and Scala may be more popular due to their deep integration to .NET and JVM.

Collapse
 
theodesp profile image
Theofanis Despoudis

It may have some uses in production but in reality, it is a drop in the ocean. It has currently only 4854 Q/A in stack overflow compared to Haskell which is 35,351. I think Haskell has attracted more interested parties in general.

In reality, I think both languages are powerful but not practical. Each has its own problems that make most programmers wonder if they can be used reliably in production. I understand that they have their use cases.

I don't mind learning new languages but I would like to build something that I will understand 6 months when I revisit the code. For me, I've found that only Python and Go can do that, it may be different for other people though.

Thread Thread
 
bobbypriambodo profile image
Bobby Priambodo • Edited

I get where you're coming from. Perhaps a world where these languages are mainstream is not going to be here any time soon, and it's also a bit of chicken-and-egg problem that you will have a hard time improving if not many use it, and you won't be able to get more people to use it if it's not improved.

Hopefully it's taking off on frontend world, though, with Elm, ClojureScript, ReasonML, and PureScript each getting a considerable growing community.

Thanks for your input!

Thread Thread
 
theodesp profile image
Theofanis Despoudis

I like ClojureScript followed by PureScript and recently I was playing with Scala.js. As for ReasonML I was thinking it may be better if you use the native module to do server-side development as it will be more suitable for it.