2 More OCaml
OCaml, recap
By now you’ve installed OCaml and written/sent your first
expression to the toplevel
Last time we wrote some basic OCaml expressions
following the below grammar:
exp value (ints, bools, chars, strings,...)
| exp + exp | exp - exp | … (binary operations)
| - exp (unary minus)
| ( exp ) (parenthesized exps)
3 More OCaml
Top-level let bindings
In ML one can bind the value of an expression to a name:
let id = exp
For example:
let x = 3;;
let y = 4;;
x + y;;
Important note: this is not an assignment!
An assignment has state, i.e., a little piece of
memory that can (and will) change under your
feet…
4 More OCaml
Nested let bindings
One can also locally bind a value to a name locally within an expression:
let id = exp in exp
Confusingly this is also expressed with the let
keyword(!)
For example:
let x = 3 in x * x * x gives 27
but afterwards x is no longer visible:
# x+x;;
Error: Unbound value x
5 More OCaml
OCaml syntax, recap
A grammar can formally distinguish top-level lets from the
nested, expression-level lets :
topdecl exp
| let id = exp (top-level let)
exp id
| value
| exp + exp | exp - exp | …
| - exp
| ( exp )
| let id = exp in exp (expr-level let)
6 More OCaml
Functions (are fun) (1/2)
Functions are written with the fun keyword:
fun id … id -> exp
For example: fun x -> x * x
has the type: int -> int
We can bind the function value to a name:
let square = fun x -> x * x
and call it:
# square 4;;
- : int = 16
#
7 More OCaml
Functions (are fun) (2/2)
It is so common to bind a function value to a name that there is a short hand notation:
let funname id … id = exp
For example: let square x = x * x
One can also locally define functions with similar short hand notation:
let funname id … id = exp in exp
For example: let quadruple n =
let double m = m + m in
double (double n)
8 More OCaml
Recursive functions
Recursive functions are explicitly marked as such with the rec keyword:
let rec funname id … id = exp
For example:
let rec fac n =
if n = 0
then 1
else n * fac (n - 1)
To which OCaml responds:
val fac : int -> int = <fun>
9 More OCaml
Mutually recursive functions
Recursive functions that call each other should be declared simultaniously with and.
For example: let rec is_even n =
if n = 0
then true
else is_odd (n - 1)
and is_odd n =
if n = 0
then false
else is_even (n - 1)
to which OCaml responds:
val is_even : int -> bool = <fun>
val is_odd : int -> bool = <fun>
10 More OCaml
Pattern matching (1/5)
One typically destructs data using pattern
matching:
match exp with
| pattern -> exp
| pattern -> exp
…
For example:
let bool_to_string b = match b with
| true -> "true"
| false -> "false"
11 More OCaml
Pattern matching (2/5)
OCaml’s pattern matching compiler helps ensure
that you didn’t miss a case:
let is_valid_bool s = match s with | "true" -> true
| "false" -> true
un>
12 More OCaml
Pattern matching (2/5)
OCaml’s pattern matching compiler helps ensure
that you didn’t miss a case:
let is_valid_bool s = match s with | "true" -> true
| "false" -> true
Warning 8: this pattern-matching is not
exhaustive.
Here is an example of a value that is not
matched:
""
val is_valid_bool : string -> bool = <fun>
13 More OCaml
Pattern matching (3/5)
The wildcard pattern can be used to catch
”default” behavior:
which will satisfy OCaml:
The exhaustiveness check is your friend.
Do not use wildcards everywhere just to “make it shut up”.
let is_valid_bool s = match s with
| "true" -> true
| "false" -> true
| _ -> false
val is_valid_bool : string -> bool = <fun>
14 More OCaml
Pattern matching (4/5)
Beware that the pattern order now matters:
let is_valid_bool s = match s with
| _ -> false
| "true" -> true
| "false" -> true
15 More OCaml
Pattern matching (4/5)
Beware that the pattern order now matters:
let is_valid_bool s = match s with
| _ -> false
| "true" -> true
| "false" -> true
Warning 11: this match case is unused.
Warning 11: this match case is unused.
val is_valid_bool : string -> bool = <fun>
16 More OCaml
Pattern matching (5/5)
Patterns can also bind variables which will be
visible within the pattern’s right-hand-side:
to which OCaml responds:
let rec fac n = match n with
| 0 -> 1
| n -> n * fac (n - 1)
val fac : int -> int = <fun>
17 More OCaml
Tuples
Tuples are one way to combine types to build new ones:
type point3d = int * int * int
which declares point3d as a short hand for int triples
OCaml will infer tuple types (they don’t need to be declared):
One can project data from pairs with fst and snd:
# let mypair = (1,2);;
val mypair : int * int = (1, 2)
# snd mypair;;
- : int = 2
18 More OCaml
Tuple matching
One can also pattern match on tuple types using let:
let distance_from_origo p =
let (x,y) = p in
let sqr_dist = (x * x) + (y * y) in
sqrt (float_of_int sqr_dist)
for which OCaml infers the type:
Alternatively one can pattern match directly in the function
header:
let distance_from_origo' (x,y) =
let sqr_dist = (x * x) + (y * y) in
sqrt (float_of_int sqr_dist)
val distance_from_origo : int * int -> float = <fun>
19 More OCaml
Lists
Lists are created inductively from the empty list [] and the
cons operator ::
In Java we would (probably) write this as List<Integer>
As a short hand one can also write list literals with square
brackets and semicolon as element separator:
One can concatenate lists with @:
# mylist@mylist;;
- : int list = [1; 2; 3; 1; 2; 3]
# let mylist' = [0;1;2;3];;
val mylist' : int list = [0; 1; 2; 3]
# let mylist = 1::2::3::[];;
val mylist : int list = [1; 2; 3]
20 More OCaml
Lists, polymorphically
We can now write structurally recursive functions
over lists:
let rec length l = match l with
| [] -> 0
| elem::elems -> 1 + length elems
For which OCaml will infer the polymorphic type:
The corresponding generic Java method would accept a List<X> and return a Java int
val length : 'a list -> int = <fun>
21 More OCaml
Data types (1/3)
One can also form new types by creating disjoint unions
(aka algebraic data types)
They represent a variant (like an enum in C or Java)
with a limited number of choices.
For example: type mybool =
| Mytrue
| Myfalse
22 More OCaml
Data types (1/3)
One can also form new types by creating disjoint unions
(aka algebraic data types)
They represent a variant (like an enum in C or Java)
with a limited number of choices.
For example: type mybool =
| Mytrue
| Myfalse
Note: the constructors have to be upper case
Values are created with the constructors:
# Mytrue;;
- : mybool = Mytrue
23 More OCaml
Data types (2/3)
We can process the data types using pattern matching
There is typically one case per constructor
For example:
let mybool_to_bool mb = match mb with
| Mytrue -> …
| Myfalse -> …
24 More OCaml
Data types (2/3)
We can process the data types using pattern matching
There is typically one case per constructor
For example:
let mybool_to_bool mb = match mb with
| Mytrue -> true
| Myfalse -> false
In this way we let the types guide us
25 More OCaml
Data types (3/3)
Data types can also carry data:
type card =
| Clubs of int
| Spades of int
| Hearts of int
| Diamonds of int
which we also extract by pattern matching:
let card_to_string c = match c with
| Clubs i -> …
| Spades i -> …
| Hearts i -> …
| Diamonds i -> …
26 More OCaml
Data types (3/3)
Data types can also carry data:
type card =
| Clubs of int
| Spades of int
| Hearts of int
| Diamonds of int
which we also extract by pattern matching:
let card_to_string c = match c with
| Clubs i -> (string_of_int i) ^ " of clubs"
| Spades i -> (string_of_int i) ^ " of spades"
| Hearts i -> (string_of_int i) ^ " of hearts"
| Diamonds i -> (string_of_int i) ^ " of diamonds"
27 More OCaml
Polymorphic data types
OCaml comes with a builtin option type
type 'a option =
| None
| Some of 'a
Note: option is polymorphic – it can carry any kind of data
This is handy for signalling “data is there / data isn’t there”
For example:
let first_elem l = match l with
| [] -> …
| e::es -> …
28 More OCaml
Polymorphic data types
OCaml comes with a builtin option type
type 'a option =
| None
| Some of 'a
Note: option is polymorphic – it can carry any kind of data
This is handy for signalling “data is there / data isn’t there”
For example:
let first_elem l = match l with
| [] -> None (*no first element!*)
| e::es -> …
29 More OCaml
Polymorphic data types
OCaml comes with a builtin option type
type 'a option =
| None
| Some of 'a
Note: option is polymorphic – it can carry any kind of data
This is handy for signalling “data is there / data isn’t there”
For example:
let first_elem l = match l with
| [] -> None
| e::es -> Some e (*first elem present*)
30 More OCaml
Polymorphic data types
OCaml comes with a builtin option type
type 'a option =
| None
| Some of 'a
Note: option is polymorphic – it can carry any kind of data
This is handy for signalling “data is there / data isn’t there”
For example:
let first_elem l = match l with
| [] -> None
| e::es -> Some e (*first elem present*)
for which OCaml infers a polymorphic type:
val first_elem : 'a list -> 'a option = <fun>
31 More OCaml
Example: arithmetic expressions (1/4)
Here’s a data type representing arithmetic expressions close
to what you’ve seen in dProgSprog:
type aexp =
| Lit of int
| Plus of aexp * aexp
| Times of aexp * aexp
Let’s build a little tree data structure representing 1+2*3:
# let mytree = Plus (Lit 1, Times (Lit 2, Lit 3));;
val mytree : aexp = Plus (Lit 1, Times (Lit 2, Lit 3))
32 More OCaml
Example: arithmetic expressions (2/4)
Inductive datatypes and recursive functions make a powerful
cocktail:
let rec interpret ae = match ae with
| Lit i -> …
| Plus (ae0, ae1) ->
…
| Times (ae0, ae1) ->
…
33 More OCaml
Example: arithmetic expressions (2/4)
Inductive datatypes and recursive functions make a powerful
cocktail:
let rec interpret ae = match ae with
| Lit i -> i
| Plus (ae0, ae1) ->
…
| Times (ae0, ae1) ->
…
34 More OCaml
Example: arithmetic expressions (2/4)
Inductive datatypes and recursive functions make a powerful
cocktail:
let rec interpret ae = match ae with
| Lit i -> i
| Plus (ae0, ae1) ->
let v0 = interpret ae0 in
…
| Times (ae0, ae1) ->
…
35 More OCaml
Example: arithmetic expressions (2/4)
Inductive datatypes and recursive functions make a powerful
cocktail:
let rec interpret ae = match ae with
| Lit i -> i
| Plus (ae0, ae1) ->
let v0 = interpret ae0 in
let v1 = interpret ae1 in
…
| Times (ae0, ae1) ->
…
36 More OCaml
Example: arithmetic expressions (2/4)
Inductive datatypes and recursive functions make a powerful
cocktail:
let rec interpret ae = match ae with
| Lit i -> i
| Plus (ae0, ae1) ->
let v0 = interpret ae0 in
let v1 = interpret ae1 in
v0 + v1
| Times (ae0, ae1) ->
…
37 More OCaml
Example: arithmetic expressions (2/4)
Inductive datatypes and recursive functions make a powerful
cocktail:
let rec interpret ae = match ae with
| Lit i -> i
| Plus (ae0, ae1) ->
let v0 = interpret ae0 in
let v1 = interpret ae1 in
v0 + v1
| Times (ae0, ae1) ->
let v0 = interpret ae0 in
let v1 = interpret ae1 in
v0 * v1
38 More OCaml
Example: arithmetic expressions (2/4)
Inductive datatypes and recursive functions make a powerful
cocktail:
let rec interpret ae = match ae with
| Lit i -> i
| Plus (ae0, ae1) ->
let v0 = interpret ae0 in
let v1 = interpret ae1 in
v0 + v1
| Times (ae0, ae1) ->
let v0 = interpret ae0 in
let v1 = interpret ae1 in
v0 * v1
Now let’s run the thing: # interpret mytree;;
- : int = 7
39 More OCaml
Example: arithmetic expressions (3/4)
By a similar traversal we can serialize the tree into a string:
let rec exp_to_string ae = match ae with
| Lit i ->
…
| Plus (ae0, ae1) ->
…
| Times (ae0, ae1) ->
…
40 More OCaml
Example: arithmetic expressions (3/4)
By a similar traversal we can serialize the tree into a string:
let rec exp_to_string ae = match ae with
| Lit i ->
string_of_int i
| Plus (ae0, ae1) ->
let s0 = exp_to_string ae0 in
let s1 = exp_to_string ae1 in
"(" ^ s0 ^ "+" ^ s1 ^ ")"
| Times (ae0, ae1) ->
let s0 = exp_to_string ae0 in
let s1 = exp_to_string ae1 in
"(" ^ s0 ^ "*" ^ s1 ^ ")"
41 More OCaml
Example: arithmetic expressions (3/4)
By a similar traversal we can serialize the tree into a string:
let rec exp_to_string ae = match ae with
| Lit i ->
string_of_int i
| Plus (ae0, ae1) ->
let s0 = exp_to_string ae0 in
let s1 = exp_to_string ae1 in
"(" ^ s0 ^ "+" ^ s1 ^ ")"
| Times (ae0, ae1) ->
let s0 = exp_to_string ae0 in
let s1 = exp_to_string ae1 in
"(" ^ s0 ^ "*" ^ s1 ^ ")"
And again run the thing: # exp_to_string mytree;;
- : string = "(1+(2*3))"
42 More OCaml
Example: arithmetic expressions (4/4)
Now suppose we have a little target language for a simple
stack machine:
type inst =
| Push of int
| Add
| Mult
We can now write a compiler from arithmetic expressions
into this type by a similar traversal…
43 More OCaml
Example: arithmetic expressions (4/4)
Again there are three cases to consider:
let rec compile ae = match ae with
| Lit i ->
…
| Plus (ae0, ae1) ->
…
| Times (ae0, ae1) ->
…
44 More OCaml
Example: arithmetic expressions (4/4)
Again there are three cases to consider:
let rec compile ae = match ae with
| Lit i ->
[Push i]
| Plus (ae0, ae1) ->
let is0 = compile ae0 in
let is1 = compile ae1 in
is0 @ is1 @ [Add]
| Times (ae0, ae1) ->
let is0 = compile ae0 in
let is1 = compile ae1 in
is0 @ is1 @ [Mult]
45 More OCaml
Example: arithmetic expressions (4/4)
Again there are three cases to consider:
let rec compile ae = match ae with
| Lit i ->
[Push i]
| Plus (ae0, ae1) ->
let is0 = compile ae0 in
let is1 = compile ae1 in
is0 @ is1 @ [Add]
| Times (ae0, ae1) ->
let is0 = compile ae0 in
let is1 = compile ae1 in
is0 @ is1 @ [Mult]
# compile mytree;;
- : inst list = [Push 1; Push 2; Push 3; Mult; Add]
46 More OCaml
References and side-effects
Assignment is available in OCaml
You allocate a mutable cell in memory with ref:
let mycell = ref 0
One can assign a new value with :=
mycell := 42
And read off the content by dereferencing the address:
The standard library also comes with functions incr and
decr for incrementing or decrementing an integer
reference, respectively
# !mycell;;
- : int = 42
47 More OCaml
Records
Records are a viable alternative to tuples as they name
each entry with a label
Records need to be declared up front:
type identifier = { pos : int * int;
id : string }
After which record values can be created:
let someid = { pos = (2,3);
id = "x" }
Entries are looked up using the familiar dot syntax: let error unknownid =
print_endline (unknownid.id ^ " is not defined")
# error someid;;
x is not defined
48 More OCaml
Exceptions
Exceptions are declared with the exception keyword:
exception Darn_list_is_empty
They are created with the constructor and thrown with raise:
let first_elem' l = match l with
| [] -> raise Darn_list_is_empty
| e::es -> e
Finally they are handled with the try/with construct:
try first_elem' []
with Darn_list_is_empty -> 0
The standard library
OCaml includes a decent standard library:
http://caml.inria.fr/pub/docs/manual-ocaml/libref/index.html
All bindings in the module Pervasives are
available in the top-level.
Many of the functions we have covered (and
more) come from Pervasives - so have a look
49 More OCaml