OCaml at a Glance — A Language a Day, Advent Calendar 2019 Day 13/24

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 -o key:

$ ocamlc -o helloworld helloworld.ml
$ ./helloworld

To run a program as a script, simply run ocaml:

$ ocaml helloworld.ml

Hello, World!

Here is a possible initial program in OCaml:

print_endline "Hello, World!"

Variables

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 ^ operator.

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.

Here, ;; 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
;;

print_string name
;;

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

Functions

You can define a function with the same keyword let:

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

Anonymous functions

You can define an anonymous function using the fun keyword:

print_int ((fun x -> x * x) 5)

Lists

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
;;

The matchwith 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 sum function:

let data = [10; 20; 30] in
  print_int (sum data);
  print_string "\n";;

Object-oriented programming

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.

Objects

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, name and 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 # symbol.

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.

Classes

Let us convert the new_person object from the previous section to a real class definition.

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 new keyword.

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

Concurrency

You can do concurrency with the Threads and Async modules. Let me show the implementation of the Sleep Sort algorithm that I came up with.

Sleep Sort

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.

Here, the 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 List.map.

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

Get more

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

Leave a Reply