About this ‘A Language a Day’ Advent Calendar 2019
Welcome to Day 13 of this year’s A Language a Day Advent Calendar. Today’s topic is introduction to the OCaml programming language.
Facts about the language
Some facts about OCaml:
- Based on Caml (which is based on ML)
- Static type checking
- Supports functional, imperative, and object-oriented programming
- Appeared in 1996
- Website: ocaml.org
- (Should not be confused with Perl’s camel)
Installing and running OCaml
The official website contains a lot of instructions for different operating systems. For the Mac OS users, the commands are the following:
$ brew install ocaml $ brew install opam
The second command installs OPAM, the package manager (we don’t need it for running the examples in this article).
To compile a program, run the
ocamlc compiler, which will generate a binary file
a.out, or specify the output file in the
$ ocamlc -o helloworld helloworld.ml $ ./helloworld
To run a program as a script, simply run
$ ocaml helloworld.ml
Here is a possible initial program in OCaml:
print_endline "Hello, World!"
To create a variable, use the
let keyword. Here is an example of a program that uses two variables to print a message.
let name = "John";; let greeting = "Hello, " ^ name ^ "!\n";; print_string greeting;;
Notice how the string concatenation is done with the
Disclaimer. What was just shown, is called let-binding. With it, we give a name to an expression on the right side of the
=. If you want to understand it deeper, as well as read about references, I would suggest you to check the the corresponding chapter in the official tutorial.
;; vs ; vs in
You may have noticed the double semicolon at the end of lines in the previous example. It may be quite confusing for programmers in other languages.
;; is a statement terminator: it ends a block of code. It does not end every small bit of a code. (To some extent, it resembles the commit command when you want to finish a transaction in SQL.) In the example above, the
name and the
greeting variables are global, and you can use it later in the code. Although, you can make them local if you use
in instead of
let name = "John" in let greeting = "Hello, " ^ name ^ "!\n" in print_string greeting
This code is actually a single big construct like
let name = expr1 in expr2 in expr3. If you try to print the name after it, it will not be visible:
let name = "John" in let greeting = "Hello, " ^ name ^ "!\n" in print_string greeting ;;
This program cannot be compiled because of the
Unbound value name error. The use of the
name variable in the second call of
print_string is not possible as it was local to the previous part of the program that ended with
If you make the variable global, you are able to use it later too:
let name = "John" ;; let greeting = "Hello, " ^ name ^ "!\n" in print_string greeting ;; print_string name ;;
A single semicolon is the sequence operator. It can roughly be compared to the
, operator in C. In the following example, it is used to separate the two print expressions:
let name = "John" in let greeting = "Hello " in print_string greeting ; print_endline name
You can define a function with the same keyword
let greet name = "Hello, " ^ name ^ "!\n";; print_string (greet "John");;
Calling a function does not require putting its arguments in parentheses, but you may need to add them to group the result to pass it to the next function, as shown in this example.
If you need more than one argument, list them space-separated:
let add a b = a + b ;; print_int (add 40 2) ;; (* 42 *)
A Factorial example
To define a factorial function recursively, we need to mention its name inside the body of the function. To make it possible, add the
rec word to its definition:
let rec factorial n = if n == 1 then 1 else n * factorial(n - 1);; print_int (factorial 5);; (* 120 *)
You can define an anonymous function using the
print_int ((fun x -> x * x) 5)
Let us create a list of integers and find the sum of its elements. Here is the list:
let data = [10; 20; 30]
Another form of list creation is using the
:: separator (note that you have to terminate the list with an empty list
let data = 10 :: 20 :: 30 :: 
To compute the sum, we need to traverse the list, but in a functional programming language you’d rather do it recursively. So, let’s define a recursive function:
let rec sum lst = match lst with |  -> 0 | head :: tail -> head + sum tail ;;
with construct splits the execution in two branches. If the
lst is an empty list
, then the result is
0. If it contains elements, then we try to split it into its head and tail:
head :: tail and define the result as a sum of the
head element and the sum of the rest elements:
head + sum tail. The recursion stops when
tail becomes an empty list.
Here is how you can use the
let data = [10; 20; 30] in print_int (sum data); print_string "\n";;
The O letter in the language name originally stood for Objective, as OCaml adds the possibility to use object-oriented programming to the Caml language.
Let us look at a draft object defining a person with the name and the age.
let person = object val mutable name = "?" val mutable age = 0 method inc_age = age <- age + 1 method setname newname = name <- newname method getname = name end;; person#setname "John"; print_endline person#getname;; (* John *)
This example contains a lot of code, and it defines an object (not a class!) with the
object keyword. The object has two data fields,
age, and three
methods. Getting and setting one of the fields is done via the corresponding getter and setter methods. Notice the way you call methods using the
The above draft code can only create a single object, so let’s wrap it with a function so that you can create many objects with different names and ages:
let new_person name age = object val name : string = name val mutable age : int = age method info = Printf.sprintf "%s is %i." name age end let john = new_person "John" 32;; let alla = new_person "Alla" 31;; print_endline john#info; (* John is 32 *) print_endline alla#info;; (* Alla is 31 *)
The main difference here is that you pass both the name and the age as parameters to the
new_person function. An additional piece of a new OCaml syntax element here is an implicit type annotation of the variables:
let name : string. The output string is prepared via the
Printf.sprintf function; I hope you understand its usage instantly.
Let us convert the
new_person object from the previous section to a real
class person name age = object val name : string = name val mutable age : int = age method info = Printf.sprintf "%s is %i." name age end;;
Now, you can create the new instances with the
let john = new person "John" 32 in print_endline john#info; (* John is 32. *) let alla = new person "Alla" 31 in print_endline alla#info;; (* Alla is 31. *)
A Polymorphic example
Speaking briefly, use the
inherit keyword to create a child class:
class animal = object method info = "Animal" end class dog init = object inherit animal method info = "Dog" end class cat init = object inherit animal as super method info = "Cat is an " ^ super#info end let zoo = [new dog(); new cat()];; List.iter (fun x -> print_endline x#info) zoo
In this example, the
dog and the
cat classes are constructed a bit differently just to show how you can access a parent’s method with the same name via
super. In the output, the dogs simply tell who they are, and the cats also mention their base class.
In a loop, we iterate over a list using the
List.iter function from the standard library.
As a hint, don’t forget that you separate list items with a semicolon, not with a comma (I spent a lot of time trying to find the error in this code).
You can do concurrency with the
Async modules. Let me show the implementation of the Sleep Sort algorithm that I came up with.
let sort_me x = Thread.delay (Float.of_int x); print_int x; print_string "\n";; let data = [10; 4; 2; 6; 2; 7; 1; 3];; List.map (Thread.create sort_me) data;; Thread.delay 11.
Thread.create function is used to create a thread, and
Thread.delay sleeps the given number of seconds. As this function expects a floating-point value, and the data elements are integers, we have to manually convert it. Note also how the data is directly converted to a list of the
Thread.t objects using
To run the code, you need to add the library using the command-line options:
$ ocaml -I +threads unix.cma threads.cma sleep_sort.ml
If you ask me what is OCaml, I would answer that it is a Universe. It encapsulates a lot of concepts that are not always easy to understand for a person used to program in C-like languages.
I can recommend you start with the following two places:
The source code of today’s article are uploaded to GitHub.
Next: Day 14. Clojure