About this ‘A Language a Day’ Advent Calendar
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 match
… 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 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 method
s. 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
One thought on “OCaml at a Glance — A Language a Day, Advent Calendar Day 13/24”