Variables
So, to declare variables we can use let
keyword. For example:
let x = 1;;
We can also use ;;
to separate expressions. For example:
let x = 1;;
let y = 2;;
We can do something like this:
let x = 50 in x * x;;
Obs: It will return
2500
. Butx
is not defined outside ofin
block. So, we can’t usex
outside ofin
block. If you try to print outx
value, you will have an error.
Functions
We can define functions with let
keyword. For example:
let square x = x * x;;
Anonymous functions are defined with fun
keyword. Example:
map (fun x -> x * 2) [1; 2; 3];;
Recursion
We can do recursion with let rec
keyword. For example:
let rec range a b =
if a > b then []
else a :: range (a + 1) b;;
Obs: The difference between
let
andlet rec
is that if we have usedlet
we couldn’t userange
function inside ofrange
function, we would have an error.
Float x Int
OCaml needs you to explicitly convert int
to float
and vice versa. For example:
let x = 1;;
let y = 2.0;;
let z = float_of_int x +. y;;
Obs: The
+.
operator is used to sumfloat
numbers. If you try to sumint
numbers with+.
operator, you will have an error. The same for the otherwise case.
Pattern Matching
We can use the match
and with
keywords to do pattern matching. For example:
let rec factorial n =
match n with
| 0 | 1 -> 1
| _ -> n * factorial (n - 1);;
Obs: The
_
is used to match any value. We could replace the last line with| x -> x * factorial (x - 1)
. It would be the same. Thex
is a variable that receives the value ofn
in the case. Of coursex
doesn’t exist outside of the the case.
We can also simplify the pattern matching by using the function
keyword. For example:
let rec factorial = function
| 0 | 1 -> 1
| n -> n * factorial (n - 1);;
Obs: The
function
keyword introduces pattern matching directly. The parameter is not named, but we can use it in the cases (hmm).
Lists
Lists are ordered collections of elements of the same type. For example:
# [];;
- : 'a list = []
# [1; 2; 3];;
- : int list = [1; 2; 3]
# [false; false; true];;
- : bool list = [false; false; true]
# [[1; 2]; [3; 4]; [5; 6]];;
- : int list list = [[1; 2]; [3; 4]; [5; 6]]
The ::
(cons) operator adds an element to the beginning of a list. For example:
# 1 :: [];;
- : int list = [1]
# 2 :: [1];;
- : int list = [2; 1]
The @
(append) operator concatenates two lists. For example:
# [1; 2] @ [3; 4];;
- : int list = [1; 2; 3; 4]
Non-empty lists have a head
(it’s first element) and a tail
(the list of the rest of the elements). For example:
# let x = [1; 2; 3];;
val x : int list = [1; 2; 3]
# List.hd x;;
- : int = 1
# List.tl x;;
- : int list = [2; 3]
We can create functions that operates over lists using pattern matching. For example:
# let rec total l =
match l with
| [] -> 0
| h :: t -> h + total t;;
val total : int list -> int = <fun>
# total [1; 3; 5; 3; 1];;
- : int = 13
Obs: The
h
is the head and thet
is the tail. We use the::
operator to desconstruct here.
Another example:
let rec length l =
match l with
| [] -> 0
| _ :: t -> 1 + length t;;
Obs: This function will not operate just a list of integers as the latest example, it’ll operates lists of any types. So the list in the parameter is of type
'a list
(pronounced alpha list), and the function is called polymorphic since the type isn’t relevant.
Let’s write a map
function that will take a function and a list as parameters and will apply the function to every element of the list and return results, just like a map:
let rec map f l =
match l with
| [] -> []
| h :: t -> f h :: map f t;;
Example of usage (using the total
function we’ve created before):
# total;;
- : int list -> int = <fun>
# map total [[1; 2]; [3; 4]; [5; 6]];;
- : int list = [3; 7; 11]
# map total [1; 2; 4];;
Error: This expression has type int but an expression was expected of type
int list
Obs: The reason the second returns an error is because total returns
int
andmap
should return alist
, that’s why in the first one we passed alist
oflists
, so for each listtotal
returned anint
and themap
function returned andlist
ofints
;
Example using anonymous function:
map (fun x -> x * 2) [1; 2; 3];;
Partial Application
We can apply a function passing only some of it’s parameters. For example:
let add x y = x + y;;
let add1 = add 1;;
add1 2;;
- : int = 3
Let’s use it with our map function:
map (add 1) [1; 2; 3];;
- : int list = [2; 3; 4]
or let’s use partial application of map function:
# map (map (add 1)) [[1; 2]; [3; 4]; [5; 6]];;
- : int list list = [[2; 3]; [4; 5]; [6; 7]]
# map (map total) [[[1; 2]; [1; 2]]; [[1; 2]; [1; 2]]];;
- : int list list = [[3; 3]; [3; 3]]
Tuples
Tuples are ordered collections of elements of different types. For example:
- let t = (1, "okay", '1');;
val t : int * string * char = (1, "okay", '1')
Records
They’re similar to tuples, but instead of having elements in a fixed order, they have named elements:
type person =
{name: string;
age: int};;
let frank =
{name: "frank";
age: 21};;
let age = frank.age;;
Exceptions
Use raise
keyword to throw exceptions, example:
let f a b =
if b = 0 then raise (E2 "division by zero") else a / b;;
Built-in polymorphic option
type:
type 'a option = None | Some of 'a;;
We may write:
let f a b =
if b = 0 then None else Some (a / b);;
Imperative OCaml
We use ref
keyword to create references:
let r = ref 0;;
We use :=
operator to assign a value to the reference:
r := 100;;
We use !
operator to de-reference so we can get the reference contents:
# !r;;
- : int = 100
Loops
For loop:
# let table n =
for row = 1 to n do
for column = 1 to n do
print_string (string_of_int (row * column));
print_string " "
done;
print_newline ()
done;;
val table : int -> unit = <fun>
While loop:
# let smallest_power_of_two x =
let t = ref 1 in
while !t < x do
t := !t * 2
done;
!t;;
val smallest_power_of_two : int -> int = <fun>
Arrays
OCaml does have arrays, like:
# let arr = [|1; 2; 3|];;
val arr : int array = [|1; 2; 3|]
# arr.(0);;
- : int = 1
# arr.(0) <- 0;;
- : unit = ()
# arr;;
- : int array = [|0; 2; 3|]
Standard Libraries
Printf module:
let print_length s =
Printf.printf "%s has %i characters\n" s (String.length s);;