The quick tour follows our standard route starting with simple expressions and slowly stepping up to functions, collections, and user defined types before covering high level program structures such as modules and classes.
Since printing is an "advanced concept" in functional languages, we start with the simple "Hello World" expression entered at the prompt of the interactive Ocaml interpreter.
# "Hello World";; - : string = "Hello World"
As you can see, string literals are enclosed in double quotes, and the double semicolon finished an expression. A single semicolon (which ends an SML expression) is used in Caml as a separator (see below). The Ocaml interpreter shows us the type and the value of the entered expression. The dash tells us that the value was not bound to any symbol.
There is one peculiarity about arithmetical expressions: Ocaml's operators are not overloaded for integers and floating point numbers. The standard operators act on integers, and the floating point operators have a period as a suffix. At least the minus sign works as expected (we don't have to use SML's tilde ~ for negative numbers).
# -3+4*5;; - : int = 17 # -3.5 +. 4.0 *. 5.0;; - : float = 16.5
If we try and use an integer operator with a float (or vice versa), we get a type error. We can explicitly convert between the two standard number types using the functions int_of_float and float_of_int.
# 1.5 + 1;; This expression has type float but is here used with type int # int_of_float 1.5 + 1;; - : int = 2
Similarly, we need yet another operator to add (i.e., concatenate) strings, the hat ^.
# "Hello " ^ "World";; - : string = "Hello World"
Concerning boolean expressions, all but the not operator are borrowed from C.
# true || false ;; - : bool = true # true && false ;; - : bool = false # not true ;; - : bool = false
The let command binds a symbol to an expression. Like in other functional languages, this is not be confused with the assignment to a variable (like, e.g., in Python). The symbol is bound once and its value does not change.
# let i = 1234;; val i : int = 1234 # i;; - : int = 1234
In a functional language, almost everything is an expression. We will nonetheless treat the more complicated ones such as functions and collections in the following sections, but the conditional expression fits into this section quite well. It is basically a readable form of C's question mark operator.
# let n = 50;; val n : int = 50 # if n < 100 then "small" else "big";; - : string = "small"
Another expression in this context is pattern matching. In its simplest form it can be viewed as the functional form of a case statement (switch in C).
# let i = 2;; val i : int = 2 # match i with 1 -> "one" | 2 -> "two" | 3 -> "three" | n -> "more";; - : string = "two"
The matching rules are separated with vertical bars, and each rule consists of a pattern, the arrow, and the expression defining the result for this pattern. The patterns are evaluated one after the other until a match is found. The result of the match expression is then the expression of the matching pattern. The "default" clause n -> "more" gives a first glimpse at the real power of Ocaml's pattern matching. The pattern n matches any integer.
There are two ways to define a function. The classical way binds a symbol to a function expression. The function expression consists of the keyword function followed by the arguments, an arrow (->), and the expression defining the result of the function when applied to the arguments.
# let times2 = function x -> 2*x;; val times2 : int -> int = <fun> # times2 55;; - : int = 110
The second way to define a function uses a shortcut notation which lets us write down the function expression directly.
# let times2 x = 2*x;; val times2 : int -> int = <fun> # times2 55;; - : int = 110
This syntax is especially convenient when defining functions with multiple arguments. If we check the types of built-in functions such as the arithmetical operators, we will notice that most of them are curried functions, that is, multiple arguments are interpreted one by one leading to a series of functions.
# (+);; - : int -> int -> int = <fun>
If we want to define such a curried function with the function syntax, we end up with a rather lengthy definition like in the following example.
# let add = function x -> function y -> x + y;; val add : int -> int -> int = <fun> # add 4 5;; - : int = 9
Using the shortcut notation, the same definition reduces to
# let add x y = x + y;; val add : int -> int -> int = <fun>
The uncurried version takes the pair of parameters as a single argument. This looks like a function with two arguments in the traditional sense, but it is actually one tuple argument as we can tell from the function type where the asterisk denotes the product or tuple type.
Recursive functions require the rec keyword.
# let rec fac n = if n<2 then 1 else n*fac(n-1);; val fac : int -> int = <fun> # fac 5;; - : int = 120
Alternatively, we could have defined the same function using pattern matching.
# let rec fac n = match n with 0 -> 1 | n -> n*fac(n-1);; val fac : int -> int = <fun>
It does not make a big difference in this example, but it turns out to be extremely useful for functions acting on recursively defined data structures.
Naturally, function are first class objects (or values) in a functional language such as Ocaml. We can pass them around like any other value and define higher order functions which have functions as arguments. A good example is again the composition of functions which can be defined in one short line.
# let compose f g x = f(g x);; val compose : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun> # let square x = x * x;; val square : int -> int = <fun> # let f = compose times2 square;; val f : int -> int = <fun> # f 5;; - : int = 50
Alternatively, we can use the longer form which clearly shows the character of the higher order function.
# let compose f g = function x -> f(g(x));; val compose : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun>
Note how Ocaml chooses the most generic type for the function compose. The specialization takes place only when composing two concrete functions.
Ocaml has the usual set of collection types we expect from a function language. We have already met the tuple in the last section when defining functions of multiple arguments.
The most important collection is of course the (linked) list whose node structure is organized like Lisp's lists leading to efficient functions acting on the head and tail of the list. The list literal uses the semicolon to separate the elements in the list. Most of the list functions are contained in the List module (more on modules below).# let l = [1; 2; 3];; val l : int list = [1; 2; 3] # List.hd l;; - : int = 1 # List.tl l;; - : int list = [2; 3] # 0 :: l;; - : int list = [0; 1; 2; 3] # List.concat;; - : 'a list list -> 'a list = <fun> # List.concat [l; l];; - : int list = [1; 2; 3; 1; 2; 3] # List.append l l;; - : int list = [1; 2; 3; 1; 2; 3] # l @ l;; - : int list = [1; 2; 3; 1; 2; 3] # List.nth l 2;; - : int = 3
Note that the list index used in the nth functions starts at zero. The concat function is equivalent to the @ operator.
On the next higher level we find the iterator functions such as map and reduce (in Ocaml fold_left and fold_right).
# List.map times2 l;; - : int list = [2; 4; 6] # List.fold_left (+) 10 l;; - : int = 16
There are also similar functions acting on two lists in parallel as well as functions looking for particular elements in a list.
# List.map2 (+) l l;; - : int list = [2; 4; 6] # List.exists (function x -> x > 2) l;; - : bool = true # List.mem 2 l;; - : bool = true # List.mem 4 l;; - : bool = false # List.find (function x -> x > 2) l;; - : int = 3 # List.filter (function x -> x > 2) l;; - : int list = [3]
The sort function implements merge sort. It sorts a list with respect to a comparison function (which must return -1, zero, or +1 just like C's compare functions).
# List.sort;; - : ('a -> 'a -> int) -> 'a list -> 'a list = <fun> # compare;; - : 'a -> 'a -> int = <fun> # (compare 1 2, compare 1 1, compare 2 1);; - : int * int * int = -1, 0, 1 # List.sort compare [2; 1; 4; 3];; - : int list = [1; 2; 3; 4]
So, whatever we look for in terms of list processing, we will probably find it in the standard library. Caml's reference manual contains concise descriptions of all the available functions. You can also used the module browser (ocamlbrowser) to get a quick overview of the system's (and your own) libraries.
While we talking about lists and sorting, we can show another instructive example from the Ocaml tutorial. It uses two mutally recursive functions (defined at once using and) and pattern matching to implement insertion sort.
# let rec sort lst = match lst with [] -> [] | head :: tail -> insert head (sort tail) and insert elt lst = match lst with [] -> [elt] | head :: tail -> if elt <= head then elt :: lst else head :: insert elt tail;; val sort : 'a list -> 'a list = <fun> val insert : 'a -> 'a list -> 'a list = <fun>
If your main concern is fast access to an element in a collection, Ocaml's array is the data structure of choice. The array literal looks almost like a list, but decorates the delimiting brackets with vertical bars. The index operator is .().
# let a = [| 1; 2; 3 |];; val a : int array = [|1; 2; 3|] # let a = [| 1; 2; 3 |];; val a : int array = [|1; 2; 3|] # a.(1);; - : int = 2
Arrays offer most of the collection functions we have seen for lists plus some additional functions which exploit the indexing. As an example, we can iterate over the index and the elements in an array at the same time.
# Array.iteri;; - : (int -> 'a -> unit) -> 'a array -> unit = <fun> # let f i x = Printf.printf "%d: %d\n" i x;; val f : int -> int -> unit = <fun> # Array.iteri f [|10; 20; 30|];; 0: 10 1: 20 2: 30 - : unit = () # Array.mapi (+) [|10; 20; 30|];; - : int array = [|10; 21; 32|]
The first example also demonstrates the built-in functions for formatted output which are modeled after C's standard I/O library.
With arrays, we can also leave the pure functional world, since Ocaml's arrays are mutable collections. The assignment operator is the left arrow <-.
# a.(1) <- 55;; - : unit = () # a;; - : int array = [|1; 55; 3|]
Once we are changing objects, we can also talk about Ocaml's dictionary type defined in the Hashtbl (hash table) module. Using it already leaves some object-based impression. Hash tables are created with the create function (taking the expected size of the dictionary as an argument). The actual type is undetermined until the we add the first key-value pair to the dictionary. The typical put and get operations are given as replace and delete.
# let h = Hashtbl.create 100;; val h : ('_a, '_b) Hashtbl.t = <abstr> # Hashtbl.replace h "Homer" 55;; - : unit = () # h;; - : (string, int) Hashtbl.t = <abstr> # Hashtbl.replace h "Bart" 11;; - : unit = () # Hashtbl.find h "Bart";; - : int = 11
There is also an addfunction which overrides an already existing entry without actually deleting the old value. Removing the entry will reveal the old value again.
# let h = Hashtbl.create 100;; val h : ('_a, '_b) Hashtbl.t = <abstr> # Hashtbl.add h "Homer" 55;; - : unit = () # Hashtbl.add h "Homer" 66;; - : unit = () # Hashtbl.find h "Homer";; - : int = 66 # Hashtbl.find_all h "Homer";; - : int list = [66; 55] # Hashtbl.remove h "Homer";; - : unit = () # Hashtbl.find h "Homer";; - : int = 55
This behavior models bindings with nested scopes (probably reflecting the Ocaml implementation).
Similar to the higher order functions for sequences, there are functions which operate on all elements in the hash table. The iterator function iter, for example, allows us to apply a function (as "visitor") to all name-value pairs in the hash table.
# let h = Hashtbl.create 100;; val h : ('_a, '_b) Hashtbl.t = <abstr> # Hashtbl.add h "Homer" 55;; - : unit = () # Hashtbl.add h "Bart" 11;; - : unit = () # let f key value = Printf.printf "%s: %d\n" key value;; val f : string -> int -> unit = <fun> # Hashtbl.iter f h;; Homer: 55 Bart: 11 - : unit = ()
For now, we have worked with a number of Ocaml's built-in types. Ocaml has two main tools to create new types: records and variants. A record is a collection of named fields just like a record in C or other procedural languages.
# type point = {x: int; y: int};; type point = { x : int; y : int; } # let add p1 p2 = {x = p1.x + p2.x; y = p1.y + p2.y};; val add : point -> point -> point = <fun> # let p1 = {x=10; y=20};; val p1 : point = {x = 10; y = 20} # p1.x;; - : int = 10 # let p2 = {x=(-5); y=(-10)};; val p2 : point = {x = -5; y = -10} # add p1 p2;; - : point = {x = 5; y = 10}
In contrast to SML, fields are accessed with the "usual" dot notation known from the descendents of C and Pascal.
By default, records are immutable. We can not change a field of a record, but only construct completely new ones. However, adding the mutable keyword to a field allows us to change its value. Like the mutable arrays, this feature leaves the clean functional world and lets us work with dangerous (but often efficient) side effects.
# type person = {mutable name: string; age: int};; type person = { mutable name : string; age : int; } # let p = {name="Homer"; age=55};; val p : person = {name = "Homer"; age = 55} # p.age <- 66;; The record field label age is not mutable # p.name <- "Bart";; - : unit = () # p;; - : person = {name = "Bart"; age = 55}
The second tool for type creation are variant types. Variant types model alternatives.
# type color = Blue | Red | Green;; type color = Blue | Red | Green # Red;; - : color = Red
Note that Ocaml uses the type keyword for records and variants alike. We can define the equivalent of SML's option using a parameterized variant type.
# type 'a option = None | Some of 'a;; type 'a option = None | Some of 'a # None ;; - : 'a option = None # Some 55;; - : int option = Some 55
But as in SML, we don't have to define the option type ourselves since it is already part of Ocaml's standard library.
The classic example for a recursive variant type, a binary tree, looks almost like the SML equivalent.
# type 'a btree = Empty | Node of 'a * 'a btree * 'a btree;; type 'a btree = Empty | Node of 'a * 'a btree * 'a btree
Ocaml's structures bundle type and functions to consistent units. Apart from minor syntactical differences, Ocaml follow the ML mode as defined in SML. The module command (replacing SML's structure) gives a structure a name. Here is the Ocaml version of the Color structure.
# module Color = struct type color = Red | Green | Blue;; let name c = match c with Red -> "red" | Green -> "green" | Blue -> "blue";; end;; module Color : sig type color = Red | Green | Blue val name : color -> string end # Color.name Color.Red;; - : string = "red"
Signatures are to structures what types are to values. It is therefore only consistent that we bind a signature to a name using the module type command. Here is the interface for our extremely useful Color structure.
# module type COLOR = sig type color;; val name : color -> string;; val make : string -> color;; end;; module type COLOR = sig type color val name : color -> string end
Since the actual type is hidden, we have added a constructor function which is the opposite of the name function. We can now restrict the visibility of our structure by declaring it as an implementation of the signature.
# module Color : COLOR = struct type color = Red | Green | Blue;; let name c = match c with Red -> "red" | Green -> "green" | Blue -> "blue";; let make s = match s with "red" -> Red | "green" -> Green | "blue" -> Blue;; end;; Warning: this pattern-matching is not exhaustive. Here is an example of a value that is not matched: "" module Color : sig type color = Red | Green | Blue val name : color -> string val make : string -> color end
This interpreter is nice enough to warn us about the incomplete definition of the make. We will ignore this warning until we know how to indicate errors with exception.
The actual colors are now not accessible anymore. Instead, we have to construct them from the color strings.
# Color.Red;; Unbound constructor Color.Red # let c = Color.make "red";; val c : Color.color = <abstr> # Color.name c;; - : string = "red"
In contrast to SML, we see only what has been declared in the signature (similar to SML's opaque signature constraint :>).
We have finally caught up with SML and now tackle the object-oriented ML extensions implemented in Ocaml. Here is the first Ocaml version of our person class.
# class person = object val name = "Homer" val age = 55 method get_name = name method get_age = age method hello = "Hello, I'm " ^ name end;; class person : object method get_age : int method get_name : string method hello : string val age : int val name : string end # let p = new person;; val p : person = <obj> # p.name;; Unbound record field label name # p#get_name;; - : string = "Homer" # p#hello;; - : string = "Hello, I'm Homer"
Of course, the class is not very satisfactory yet, because we hard-coded the values of the attributes an have no way to change them. But at least we see the basic syntax of a class, object creation, and method call.
Attributes are just values and methods are functions which have direct access to the objects attributes. The attributes are not visible from the outside (unless exposed by a method), and the methods are called with a hash # replacing the dot we are used to from other OO languages.
As it stands, we can not change the values of the attributes, not even by a method, since they are immutable by default. So, the first option to turn the class into something useful is to make the attributes mutable and introduce setter methods. To keep the example short, we only consider the first attribute.
# class person = object val mutable name = "" method get_name = name method set_name a_name = name <- a_name method hello = "Hello I'm " ^ name end;; class person : object method get_name : string method hello : string method set_name : string -> unit val mutable name : string end # let p = new person;; val p : person = <obj> # p#get_name;; - : string = "" # p#set_name "Homer";; - : unit = () # p#hello;; - : string = "Hello I'm Homer"
Although this approach works, it is following neither functional nor object-oriented best practices. What we really would like to do is to pass the values to a constructor. In Ocaml this is accomplished by writing the class definition as a function of the initial values. Here is the according version (again reduced to the bare minimum).
# class person = fun a_name -> object val name : string = a_name method get_name = name end;; class person : string -> object method get_name : string val name : string end # let p = new person "Homer";; val p : person = <obj> # p#get_name;; - : string = "Homer"
Here we have to specify the type of the attribute, since Ocaml can't derive it otherwise. Alternatively, we could have declared the type of the initialization argument.
# class person = fun (a_name: string) -> object val name = a_name method get_name = name end;; class person : string -> object method get_name : string val name : string end
Like for function definitions, there is a shortcut syntax combining the function and class definition.
# class person (a_name: string) = object val name = a_name method get_name = name end;; class person : string -> object method get_name : string val name : string end
Next, we would like to extend the person class using inheritance. To this end, we need to add an inherit clause at the beginning of the class definition.
# class employee a_name a_no = object inherit person a_name val no: int = a_no method hello = "Hello, I'm number " ^ string_of_int(no) end;; class employee : string -> int -> object method hello : string val name : string val no : int end # let e = new employee "Homer" 1234;; val e : employee = <obj> # e#hello;; - : string = "Hello, I'm number 1234"
If we need to refer to the original class, we can bind it to a name in the inherit clause. This name, typically super, can then be used to call the implementation of a method in the parent class.
# class person (a_name: string) = object val name = a_name method hello = "Hello, I'm " ^ name end;; class person : string -> object method hello : string val name : string end # class employee (a_name: string) (a_no: int) = object inherit person a_name as super val no: int = a_no method hello = super#hello ^ ", " ^ string_of_int(no) end;; class employee : string -> int -> object method hello : string val name : string val no : int end # let e = new employee "Homer" 1234;; val e : employee = <obj> # e#hello;; - : string = "Hello, I'm Homer, 1234"
To call a method on itself, an object has to define a name for itself (by convention self) following the object keyword.
# class person name = object (self) val name = name method hello = "Hello, I'm " ^ name method print = print_string self#hello end;; class person : string -> object method hello : string method print : unit val name : string end # let p = new person "Joe";; val p : person = <obj> # p#print;; Hello, I'm Joe- : unit = ()
In this example, I have also exploited (lazy as I am) the possibility to use the same name for the initialization parameter, the attribute, and the getter method.
Generic classes (classes with type parameters) are about as simple as in Eiffel. The type variable is placed in parentheses in front of the class name.
# class ['a] point (x_init: 'a) (y_init: 'a) = object val x = x_init val y = y_init method get_x = x method get_y = y end;; class ['a] point : 'a -> 'a -> object method get_x : 'a method get_y : 'a val x : 'a val y : 'a end # let p = new point 1.5 2.5;; val p : float point = <obj> # p#get_x;; - : float = 1.5
Once we know how inheritance works, we would like solid OO design and define interfaces and their implementations. Some languages called them "abstract" (C++, Java, C#), some "deferred" (Eiffel), and Ocaml uses the notion "virtual": methods without an implementation. The interface for our simplistic account could look like this:
# class virtual account = object method virtual balance : float method virtual deposit : float -> unit method virtual withdraw : float -> unit end;; class virtual account : object method virtual balance : float method virtual deposit : float -> unit method virtual withdraw : float -> unit end
To indicate that an account implementation actually realizes this interface, we inherit from the virtual class.
# class simple_account = object inherit account val mutable balance = 0.0 method balance = balance method deposit amount = balance <- balance +. amount method withdraw amount = balance <- balance -. amount end;; class simple_account : object method balance : float method deposit : float -> unit method withdraw : float -> unit val mutable balance : float end
Clients should only see the interface, not the implementation. We therefore need another operator which changes the type of an object to a base class (up-casting). In Ocaml, this is the :> operator.
# let a = (new simple_account :> account);; val a : account = <obj> # a#deposit 100.0;; - : unit = () # a#withdraw 25.0;; - : unit = () # a#balance;; - : float = 75
As you see, the type of a is account, and therefore only the interface is visible to us.
The object-oriented paradigm is well suited for functions which can be attached to one of its arguments (which becomes the owner of the method). Symmetric functions such as comparisons are not as good a fit and often call for unconvenient solutions. Java's equals method, for example, takes an arbitrary object as an argument although this object should be of the same class as the object on which the method is called. Ocaml has an interesting solution for these binary methods. A virtual method can use the type of the owning object which is made available through a type variable in the self clause.
# class virtual comparable = object (_: 'a) method virtual compare: 'a -> int end;; class virtual comparable : object ('a) method virtual compare : 'a -> int end # class account balance = object inherit comparable val mutable balance : float = balance method balance = balance method compare other = if balance < other#balance then -1 else if balance > other#balance then 1 else 0 end;; class account : float -> object ('a) method balance : float method compare : 'a -> int val mutable balance : float end # (new account 50.0)#compare (new account 100.0);; - : int = -1
Object-oriented programing with Ocaml offers another interesting feature which can help us to enjoy the advantages of objects without giving up functional reasoning: functional objects. To stay in the functional world, we have to refrain from mutable attributes. On the other hand, we often need to change some part of an object. Functional objects are immutable objects with methods that return chnaged copies of the original object. Instead of altering the state of the object itself, we create a copy that contains the change. This operation of "copying with change" is accomplished with the {< ... >} operator. Here is an example of a "functional account".
# class account = object val balance = 0.0 method balance = balance method deposit amount = {< balance = balance +. amount >} method withdraw amount = {< balance = balance -. amount >} end;; class account : object ('a) method balance : float method deposit : float -> 'a method withdraw : float -> 'a val balance : float end # let a = new account;; val a : account = <obj> # a#balance;; - : float = 0 # let a1 = a#deposit 50.0;; val a1 : account = <obj> # a1#balance;; - : float = 50 # a#balance;; - : float = 0
The two methods deposit and withdraw do not change the account object, but create a new one with the updated balance.