Reference: https://ocaml.org/docs/tour-of-ocaml
Integer
50 ;;
50 * 50 ;;
Float
6.28 ;;
3.14 ;;
String
"This is really disco!" ;;
Boolean
true ;;
List of integers
let u = [1; 2; 3; 4] ;;
List of strings
["this"; "is"; "mambo"] ;;
Prepend to List
5 :: u ;;
If else construct
2 * if "hello" = "world" then 3 else 5 ;;
Let binding
let x = 50 ;;
Let scope
let y = 50 in y * y ;;
y ;;
Let in expression
let a = 1 in
let b = 2 in
a + b ;;
Functions
let square x = x * x ;;
square 50 ;;
square ;;
Anonymous Functions
fun x -> x * x ;;
Partial Function Application
let cat a b = a ^ " " ^ b ;;
cat "ha" "ha" ;;
let cat_hi = cat "hi" ;;
cat_hi "friend" ;;
Higher order functions
List.map ;;
List.map (fun x -> x * x) ;;
List.map (fun x -> x * x) [0; 1; 2] ;;
Side Effects
print_endline ;;
print_endline "Hello" ;;
Recursive Functions
let rec range lo hi =
if lo > hi then []
else lo :: range (lo + 1) hi ;;
range 2 5 ;;
Type Conversion and Type-Inference
2.0 +. 2.0 ;;
1 + 2.5 ;;
1 +. 2.5 ;;
float_of_int 1 *. 2.5 ;;
Lists
[] ;;
[1; 2; 3] ;;
[false; false; true] ;;
[[1;2]; [3]; [4;5;6]] ;;
1 :: [2; 3; 4] ;;
let rec sum u =
match u with
| [] -> 0
| x :: v -> x + sum v ;;
sum [1; 4; 3; 2; 5] ;;
Polymorphic Functions on Lists
let rec length u =
match u with
| [] -> 0
| _ :: v -> 1 + length v ;;
length [1; 2; 3; 4] ;;
length ["cow"; "sheep"; "cat"] ;;
length [[]] ;;
Higher-Order Function
let square x = x * x ;;
let rec map f u =
match u with
| [] -> []
| x :: u -> f x :: map f u ;;
map square [1; 2; 3; 4] ;;
Pattern Matching (…)
#show option ;;
let f opt = match opt with
| None -> None
| Some None -> None
| Some (Some x) -> Some x ;;
let g x =
if x = "foo" then 1
else if x = "bar" then 2
else if x = "baz" then 3
else if x = "qux" then 4
else 0 ;;
let g' x = match x with
| "foo" -> 1
| "bar" -> 2
| "baz" -> 3
| "qux" -> 4
| _ -> 0 ;;
fun i -> match i with 0 -> 1 ;;
Pairs and Tuples
(1, "one", 'k') ;;
([], false) ;;
let snd p =
match p with
| (_, y) -> y ;;
snd (42, "apple") ;;
Enumerated Data Type
type primary_colour = Red | Green | Blue ;;
[Red; Blue; Red] ;;
Union Data Type
type http_response =
| Data of string
| Error_code of int ;;
Data "<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"utf-8\">
<title>Dummy</title>
</head>
<body>
Dummy Page
</body>
</html>";;
Error_code 404;;
Pattern matching on Variants
type page_range =
| All
| Current
| Range of int * int;;
let colour_to_rgb colour =
match colour with
| Red -> (0xff, 0, 0)
| Green -> (0, 0xff, 0)
| Blue -> (0, 0, 0xff) ;;
let http_status_code response =
match response with
| Data _ -> 200
| Error_code code -> code ;;
let is_printable page_count cur range =
match range with
| All -> true
| Current -> 0 <= cur && cur < page_count
| Range (lo, hi) -> 0 <= lo && lo <= hi && hi < page_count ;;
Records
type person = {
first_name : string;
surname : string ;
age : int
} ;;
let gerard = {
first_name = "Gerard" ;
surname = "Huet" ;
age = 76
} ;;
let s = gerard.surname ;;
let is_teenager person =
match person with
| { age = x; _ } -> 13 <= x && x <= 19 ;;
is_teenager gerard ;;
Exceptions
10 / 0 ;;
let id_42 n = if n <> 42 then raise (Failure "Sorry") else n ;;
id_42 42;;
id_42 5;;
try id_42 0 with Failure _ -> 0 ;;
Using Result Type
#show result ;;
let id_42_res n = if n <> 42 then Error "Sorry" else Ok n ;;
id_42_res 42 ;;
id_42_res 35 ;;
match id_42_res 0 with
| Ok n -> n
| Error _ -> 0 ;;
Working with Mutable State
let r = ref 0 ;;
!r ;;
r := 42 ;;
!r ;;
let text = ref "hello " ;;
print_string !text; text := "world!"; print_endline !text ;;
Modules and the Standard Library
#show Option ;;
Option.map ;;
Option.map (fun x -> x * x) ;;
Option.map (fun x -> x * x) None ;;
Option.map (fun x -> x * x) (Some 8) ;;
List.map ;;
List.map (fun x -> x * x );;
Reference: https://ocaml.org/docs/values-and-functions
What is a a Value?
2 * 21 ;;
int_of_float ;;
int_of_float (3.14159 *. 2.0) ;;
fun x -> x * x ;;
print_endline ;;
print_endline "Hello!" ;;
Global Definitions
let the_answer = 2 * 3 * 7 ;;
Local Definitions
let d = 2 * 3 in d * 7 ;;
d ;;
let d = 2 * 3 in
let e = d * 7 in
d * e ;;
d ;;
e ;;
let d =
let e = 2 * 3 in
e * 5 in
d * 7 ;;
d ;;
e ;;
Pattern Matching on Tuples
List.split ;;
let (x, y) = List.split [(1, 2); (3, 4); (5, 6); (7, 8)] ;;
Pattern Matching on Records
type name = { first : string; last : string } ;;
let robin = { first = "Robin"; last = "Milner" } ;;
let { first; last } = robin ;;
Pattern Matching on Unit
let () = print_endline "ha ha" ;;
Pattern Matching on User-Defined Types
type live_person = int * name ;;
let age ((years, { first; last }) : live_person) = years ;;
Discarding Values using Pattern Matching
let (_, y) = List.split [(1, 2); (3, 4); (5, 6); (7, 8)] ;;
Scopes and Envirnoments
let twenty = 20 ;;
let ten = 10 in 2 * ten ;;
ten ;;
(1.0 +. sqrt 5.0) /. 2.0 ;;
let _ = (1.0 +. sqrt 5.0) /. 2.0 ;;
Inner Shadowing
let i = 21 ;;
let i = 7 in i * 2 ;;
i ;;
Same-Level Shadowing
let h = 2 * 3 ;;
let e = h * 7 ;;
let h = 7 ;;
e ;;
Applying Functions
max (21 * 2) (int_of_string "713") ;;
String.starts_with ~prefix:"state" "stateless" ;;
Application Operator
sqrt 9.0 ;;
sqrt @@ 9.0 ;;
Pipe Operator
"81" |> int_of_string
|> float_of_int
|> sqrt
|> int_of_float ;;
Anonymous Functions
fun x -> x ;;
fun x -> x * x ;;
fun s t -> s ^ " " ^ t ;;
function [] -> None | x :: _ -> Some x ;;
List.map (fun x -> x * x) [1; 2; 3; 4] ;;
Defining Global Functions
let f = fun x -> x * x ;;
let g x = x * x ;;
Defining Local Functions
let sq x = x * x in sq 7 * sq 7 ;;
sq ;;
Closures
let j = 2 * 3;;
let k x = x * j ;;
k 7 ;;
let j = 7 ;;
k 7 ;;
let m = j * 3 ;;
let max_42 = max 42 ;;
Recursive Functions
let rec fibo n =
if n <= 1 then n else fibo (n - 1) + fibo (n - 2 ) ;;
let u = List.init 10 Fun.id ;;
List.map fibo u ;;
let rec fib_loop m n i =
if i = 0 then m else fib_loop n (n + m) (i -1) ;;
let fib = fib_loop 0 1 ;;
List.init 10 Fun.id |> List.map fib ;;
Defining Functions with Multiple Parameters
let sweet_cat x y = x ^ " " ^ y ;;
sweet_cat "kitty" "cat" ;;
Anonymous Functions with Multiple Parameters
let sour_cat = fun x -> fun y -> x ^ " " ^ y ;;
sour_cat "kitty" "cat" ;;
Partial Application and Closures
let sour_kitty x = sour_cat "kitty" x ;;
let sweet_kitty = fun x -> sweet_cat "kitty" x ;;
sour_kitty "cat" ;;
sweet_kitty "cat" ;;
Types of Functions of Multiple Parameters
let dummy_cat : string -> (string -> string) = sweet_cat ;;
let bogus_cat : (string -> string) -> string = sweet_cat ;;
Tuples as Function Parameters
("felix", 1920) ;;
let spicy_cat (x, y) = x ^ " " ^ y ;;
spicy_cat ("hello", "world") ;;
Currying and Uncurrying
let uncurried_cat (x, y) = sweet_cat x y ;;
let curried_cat x y = uncurried_cat (x, y) ;;
string_of_int ;;
print_endline ;;
What makes Functions different from other Values
sqrt ;;
pred ;;
succ ;;
pred = succ ;;
Reference: https://ocaml.org/docs/basic-data-types
Integers
42 ;;
Floats
let pi = 3.14159 ;;
let tau = 2.0 *. pi ;;
let tau 2 *. pi ;;
let tau = 2 8 pi ;;
Boolean
true ;;
false ;;
false < true ;;
3 * if "foo" = "bar" then 5 else 5 + 2 ;;
3 * match "foo" = "bar" with true -> 5 | false -> 5 + 2 ;;
Characters
'd' ;;
Strings
"hello" ^ " " ^ "world!" ;;
"buenos dias".[4] ;;
Byte Sequences
String.to_bytes "hello" ;;
Arrays
[| 0; 1; 2; 3; 4; 5 |] ;;
[| 'x'; 'y'; 'z' |] ;;
[| "foo"; "bar"; "baz" |] ;;
[||] ;;
[| 'x'; 'y'; 'z' |].(2) ;;
let letter = [| 'v'; 'x'; 'y'; 'z'|] ;;
letter.(2) <- 'F' ;;
letter ;;
Lists
[0; 1; 2; 3; 4; 5 ] ;;
[ 'x'; 'y'; 'z'] ;;
[ "foo"; "bar"; "baz"] ;;
3 :: [] ;;
2 :: 3 :: [] ;;
1 :: 2 :: 3 :: [] ;;
match [1; 2; 3] with
| x :: u -> x
| [] -> raise Exit ;;
match [1; 2; 3] with
| x :: y :: u -> y
| x :: u -> x
| [] -> raise Exit ;;
Options
None ;;
Some 42 ;;
Some "hello" ;;
match Some 42 with None -> raise Exit | Some x -> x ;;
Results
Ok 42 ;;
Error "Sorry" ;;
Tuples
(3, 'K') ;;
fst (3, 'g') ;;
snd (3, 'g') ;;
let f x = match x with (h, i, j, k) -> j ;;
f (42, 'h', true, 2.0) ;;
Functions
fun x -> x * x ;;
fun x -> x * x ;;
(fun x -> x * x) 9 ;;
fun x -> x ;;
(fun x -> x) 42 ;;
(fun x -> x) "This is really disco!" ;;
let f = fun x -> x * x ;;
f 9 ;;
let g x = x * x ;;
g 9 ;;
raise ;;
fun s r -> s ^ " " ^ r ;;
let mean s r = (s + r) / 2 ;;
Unit
read_line ;;
print_endline ;;
User-Defined Types
Use the “type” keyword for defining either:
- Variant
- Record
- Aliases
Enumerated Data Types
type character_class =
| Barbarian
| Bard
| Cleric
| Fighter
| Monk
| Paladin
| Ranger
| Rogue
| Sorcerer
| Wizard ;;
type rectitude = Evil | R_Neutral | Good ;;
type firmness = Chaotic | F_Neutral | Lawful ;;
let rectitude_to_french = function
| Evil -> "Mauvais"
| R_Neutral -> "Neutre"
| Good -> "Bon" ;;
Represent weekdays and you can do ordering on the values:
type weekday =
| Monday
| Tuesday
| Wednesday
| Thursday
| Friday ;;
type weekend =
| Saturday
| Sunday ;;
Monday < Tuesday ;;
Thursday < Wednesday ;;
Constructors with Data
type commit =
| Hash of string
| Tag of string
| Branch of string
| Head
| Fetch_head
| Orig_head
| Merge_head ;;
Hash "991f6710bad011868d46edb5d609ce251d1d1854";;
Using pattern matching to convert a commit to a string:
let commit_to_string = function
| Hash sha -> sha
| Tag name -> name
| Branch name -> name
| Head -> "HEAD"
| Fetch_head -> "FETCH_HEAD"
| Orig_head -> "ORIG_HEAD"
| Merge_head -> "MERGE_HEAD" ;;
let commit_to_string' x = match x with
| Hash sha -> sha
| Tag name -> name
| Branch name -> name
| Head -> "HEAD"
| Fetch_head -> "FETCH_HEAD"
| Orig_head -> "ORIG_HEAD"
| Merge_head -> "MERGE_HEAD" ;;
commit_to_string' (Hash "991f6710bad011868d46edb5d609ce251d1d1854");;
Wrapping product types with parenthesis makes them a single parameter:
type t =
| C1 of int * bool
| C2 of (int * bool) ;;
let p = (4, false) ;;
C1 p ;;
C2 p ;;
C1 has two parameters (int and bool). C2 has one parameter of type int * bool.
Recursive Variants
A definition referring to itself is recursive.
type json =
| Null
| Bool of bool
| Int of int
| Float of float
| String of string
| Array of json list
| Object of (string * json) list ;;
Both Array and Object contain values of json.
Functions with pattern matching on recursive variants are often recursive functions.
let rec has_field name = function
| Array u ->
List.fold_left (fun b obj -> b || has_field name obj) false u
| Object u ->
List.fold_left (fun b (key, obj) -> b || key = name || has_field name obj) false u
| _ -> false ;;
Polymorphic Data Types
The Some constructor of option is polymorphic as it can be Some 42 or Some “hola”.
#show option ;;
#show list ;;
bool and unit are regular variants:
#show unit ;;
#show bool;;
Product types also behave as variant types:
type ('a, 'b) pair = Pair of 'a * 'b ;;
Pair (42, true) ;;
User-Defined Polymorphic
A binary tree:
type 'a tree =
| Leaf
| Node of 'a * 'a tree * 'a tree ;;
1
/ \
2 3
Node (1, Node(2, Leaf, Leaf), Node(3, Leaf, Leaf)) ;;
let rec sum = function
| Leaf -> 0
| Node (x, left, right) -> x + sum left + sum right ;;
sum (Node (1, Node(2, Leaf, Leaf), Node(3, Leaf, Leaf))) ;;
let rec map f = function
| Leaf -> Leaf
| Node (x, left, right) -> Node (f x, map f left, map f right) ;;
map (fun x -> x * x) (Node (1, Node (2, Leaf, Leaf), Node (3, Leaf, Leaf))) ;;
Records
type character = {
name : string;
level : int;
race : string;
class_type : character_class ;
alignment : firmness * rectitude ;
armor_class : int ;
} ;;
let ghorghor_bey = {
name = "Ghorghor Bey" ;
level = 17 ;
race = "half-ogre" ;
class_type = Fighter;
alignment = (Chaotic, R_Neutral);
armor_class = -8 ;
} ;;
ghorghor_bey.alignment ;;
ghorghor_bey.class_type ;;
ghorghor_bey.level ;;
To create new record with some values changed:
let togrev = { ghorghor_bey with name = "Togrev"; level = 20; armor_class = -6 } ;;
Pattern match on records:
match ghorghor_bey with { level; _ } -> level ;;
Type Aliases
Any type can be given a name:
type latitude_longitude = float * float ;;
A complete example: Mathematical Expressions
To symbolically output: n * (x + y) = n * x + n * y.
type expr =
| Plus of expr * expr
| Minus of expr * expr
| Times of expr * expr
| Divide of expr * expr
| Var of string ;;
let e = Times (Var "n", Plus (Var "x", Var "y")) ;;
let rec to_string = function
| Plus (e1, e2) -> "(" ^ to_string e1 ^ " + " ^ to_string e2 ^ ")"
| Minus (e1, e2) -> "(" ^ to_string e1 ^ " - " ^ to_string e2 ^ ")"
| Times (e1, e2) -> "(" ^ to_string e1 ^ " * " ^ to_string e2 ^ ")"
| Divide (e1, e2) -> "(" ^ to_string e1 ^ " / " ^ to_string e2 ^ ")"
| Var v -> v ;;
let rec distrib = function
| Times (e1, Plus (e2, e3)) ->
Plus (Times (distrib e1, distrib e2),
Times (distrib e1, distrib e3))
| Times (Plus (e1, e2), e3) ->
Plus (Times (distrib e1, distrib e3),
Times (distrib e2, distrib e3))
| Plus (e1, e2) -> Plus (distrib e1, distrib e2)
| Minus (e1, e2) -> Minus (distrib e1, distrib e2)
| Times (e1, e2) -> Times (distrib e1, distrib e2)
| Divide (e1, e2) -> Divide (distrib e1, distrib e2)
| Var v -> Var v;;
e |> distrib |> to_string |> print_endline ;;
The reverse operation of factoring out the common subexpressions:
n * x + n * y = n * (x + y).
let top_factorise = function
| Plus (Times (e1, e2), Times (e3, e4)) when e1 = e3 ->
Times (e1, Plus (e2, e4))
| Plus (Times (e1, e2), Times (e3, e4)) when e2 = e4 ->
Times (Plus (e1, e3), e4)
| e -> e ;;
top_factorise (Plus (Times (Var "n", Var "x"),
Times (Var "n", Var "y"))) ;;
Reference: https://ocaml.org/docs/lists
All elements of a list must be of the same type
[1; 2; 3] ;;
List has a head and tail.
[] ;;
[false; true; false] ;;
[1; 2; 3] ;;
[[1; 2]; [3; 4]; [5; 6]] ;;
The :: or cons operator adds element to the front of the list.
1 :: [2; 3] ;;
The @ operator combines two lists:
[1] @ [2; 3] ;;
Using functions to operate on pattern matching with lists:
let rec total l =
match l with
| [] -> 0
| h :: t -> h + total t ;;
A function to find the length of a list:
let rec length l =
match l with
| [] -> 0
| _ :: t -> 1 + length t;;
length [1; 2; 3] ;;
length ["cow"; "sheep"; "cat"] ;;
length [[]] ;;
In the pattern _ :: t, _ is not inspected as its type is not relevant.
This is a polymorphic function.
A version with the @ operator for append:
let rec append a b =
match a with
| [] -> b
| h :: t -> h :: append t b;;
A map example:
let rec map f l =
match l with
| [] -> []
| h :: t -> f h :: map f t;;
map (fun x -> x * 2) [1; 2; 3];;
map total [[1; 2]; [3; 4]; [5; 6]];;
A map variant for two lists:
List.map2 ( + ) [1; 2; 3] [4; 5; 6];;
List.iter ;;
List.iter print_endline ["frank"; "james"; "mary"];;
List.iter2 ;;
List.iter2
(fun a b -> print_endline (a ^ " " ^ b))
["frank"; "james"; "mary"]
["carter"; "lee"; "jones"];;
Notice that map2 and iter2 will fail if the lists are of unequal length:
List.map2 ( + ) [1; 2; 3] [4; 5];;
Use function mem to check if an element is a member of a list.
List.mem ;;
List.mem "frank" ["james"; "frank"; "mary"];;
List.mem [] [[1; 2]; [3]; []; [5]];;
To see if all elements are even in a list, you can go over each element of the list, keeping a boolean check, or use mem:
let all =
not (List.mem false (List.map (fun x -> x mod 2 = 0) [2; 4; 6; 8]));;
let any =
List.mem true (List.map (fun x -> x mod 2 = 0) [1; 2; 3]);;
Instead use the standard library useful functions for_all and exists:
List.for_all (fun x -> x mod 2 = 0) [2; 4; 6; 8];;
List.exists (fun x -> x mod 2 = 0) [1; 2; 3];;
The standard library has evolved into its present state with pieces of frequently-used code turned into useful general functions.
Find returns the first element of a list matching a given predicate.
List.find (fun x -> x mod 2 = 0) [1; 2; 3; 4; 5];;
List.find (fun x -> x mod 2 = 0) [1; 3; 5];;
The filter function again takes a predicate and tests it against each element in the list.
List.filter (fun x -> x mod 2 = 0) [1; 2; 3; 4; 5];;
Use partition to return a pair of list:
List.partition (fun x -> x mod 2 = 0) [1; 2; 3; 4; 5];;
For dictionaries, we can use Map or Hashtbl modules.
For smaller list of pairs, List has useful functions.
List.assoc ;;
List.assoc 4 [(3, "three"); (1, "one"); (4, "four")] ;;
List.mem_assoc ;;
List.mem_assoc 4 [(3, "three"); (1, "one"); (4, "four")] ;;
You can make a list of pairs from a pair of lists and vice versa:
List.split ;;
List.split [(3, "three"); (1, "one"); (4, "four")] ;;
List.combine ;;
List.combine [3; 1; 4] ["three"; "one"; "four"];;
The compare function can compare any two values of like type:
compare ;;
compare 3 5 ;;
compare 5 3 ;;
compare 5 5 ;;
Use List.sort with a comparison function.
List.sort ;;
List.sort compare [1; 4; 6; 4; 1] ;;
Fun.flip ;;
List.sort compare ["Reynolds"; "Smith"; "Barnes"];;
List.sort (Fun.flip compare) [1; 4; 6; 4; 1];;
List.sort compare [(1, 3); (1, 2); (2, 3); (2, 2)];;
List.sort
(fun a b -> compare (fst a) (fst b))
[(1, 3); (1, 2); (2, 3); (2, 2)];;
List.fold_left ;;
List.fold_left ( + ) 0 [1; 2; 3] ;;
(* (+(+(+ 0 1) 2) 3) *)
The max function
max ;;
List.fold_left max min_int [2; 4; 6; 0; 1] ;;
The append and map functions using List.fold_right:
List.fold_right ;;
List.fold_right ( + ) [1; 2; 3] 0 ;;
(* (+ 1 (+ 2(+ 3 0))) *)
let append x y =
List.fold_right (fun e a -> e :: a) x y;;
let map f l =
List.fold_right (fun e a -> f e :: a) l [] ;;
See: hasura/graphql-engine#2933 (comment)
List.concat to convert a list of lists into a list can be expensive, since larger and larger items are passed to the @ operator as its first argument:
let concat l = List.fold_left ( @ ) [] l ;;
More examples:
let length' l =
List.fold_left (fun a _ -> a + 1) 0 l;;
let rev' l =
List.fold_left (fun a e -> e :: a) [] l;;
let split' l =
List.fold_right
(fun (x, y) (xs, ys) -> (x :: xs, y :: ys))
l
([], []);;
For longer lists, the length function may overflow the stack.
Use an accumulating parameter for the length tail-recursive function:
let rec length acc l =
match l with
| [] -> acc
| _ :: t -> length (acc + 1) t;;
let l = length 0 [1; 2; 3] ;;
We can write a wrapper helper function:
let rec length_inner acc l =
match l with
| [] -> acc
| _ :: t -> length_inner (acc + 1) t;;
let length l = length_inner 0 l;;
Or, all in one function:
let length l =
let rec length_inner acc l =
match l with
| [] -> acc
| _ :: t -> length_inner (acc + 1) t
in
length_inner 0 l;;
Reference: https://ocaml.org/docs/loops-recursion
Syntax of for loop:
for variable = start_value to end_value do
expression
done
for variable = start_value downto end_value do
expression
done
OCaml does not have break, continue or last statements.
You can throw an exception.
The for loop should evaluate and return unit.
for i = 1 to 10 do i done ;;
Syntax for while:
while boolean-condition do
expression
done
You cannot break out of while, and while loops are second-class citizens.
While loops can be used with references.
let quit_loop = ref false in
while not !quit_loop do
print_string "Have you had enough yet? (y/n) ";
let str = read_line () in
if str.[0] = 'y' then quit_loop := true
done ;;
let my_list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10] ;;
let f elem =
Printf.printf "I am looking at element %d now\n" elem
in
List.iter f my_list ;;
List.map (( * ) 2) my_list ;;
let is_even i =
i mod 2 = 0
in
List.filter is_even my_list ;;
List.mem 12 my_list ;;
List.for_all and List.exists are the “forall” and “exist” operators in predicate logic.
To operate on two lists, you have iter2, map2, for_all2, exists2 functions.
List.map2 (fun x y -> x + y) [1; 2; 3] [2; 3; 4] ;;
The map and filter functions operate on each individual list elements in isolation.
Fold is like “inserting an operator between each element of the list.”
For example, adding numbers in a list:
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 ;;
Sum of an empty list should be zero, instead of error.
Product of empty list should return one.
To handle left and right-associative operations based on parenthesis, there are left and right fold operations.
List.fold_right is less efficient.
Define sum and product functions using List.fold_left for integer lists:
let sum = List.fold_left ( + ) 0 ;;
let product = List.fold_left ( * ) 1 ;;
sum my_list ;;
product my_list ;;
Factorial example:
let rec range a b =
if a > b then []
else a :: range (a + 1) b;;
let fact n = product (range 1 n) ;;
fact 10 ;;
String module has String.copy, String.iter functions and more.
Approach 1: Read entire file using really_input method.
This will not work with keyboard input, channels and sockets.
Approach 2: Use a while loop
Approach 3: Use recursion.
Approach 1 code example:
open Printf
let read_whole_chan chan =
let len = in_channel_length chan in
let result = (Bytes.create len) in
really_input chan result 0 len;
(Bytes.to_string result)
let read_whole_file filename =
let chan = open_in filename in
read_whole_chan chan
let main () =
let filename = Sys.argv.(1) in
let str = read_whole_file filename in
printf "I read %d characters from %s\n" (String.length str) filename ;;
Approach 2 code example:
open Printf
let read_whole_chan chan =
let buf = Buffer.create 4096 in
try
while true do
let line = input_line chan in
Buffer.add_string buf line;
Buffer.add_char buf '\n'
done;
assert false (* This is never executed
(always raises Assert_failure). *)
with
End_of_file -> Buffer.contents buf
let read_whole_file filename =
let chan = open_in filename in
read_whole_chan chan
let main () =
let filename = Sys.argv.(1) in
let str = read_whole_file filename in
printf "I read %d characters from %s\n" (String.length str) filename ;;
assert false has a polymorphic type, so it will unify with whatever value is returned by the with branch.
Approach 3 code example:
open Printf
let read_whole_chan chan =
let buf = Buffer.create 4096 in
let rec loop () =
let line = input_line chan in
Buffer.add_string buf line;
Buffer.add_char buf '\n';
loop ()
in
try loop () with
End_of_file -> Buffer.contents buf
let read_whole_file filename =
let chan = open_in filename in
read_whole_chan chan
let main () =
let filename = Sys.argv.(1) in
let str = read_whole_file filename in
printf "I read %d characters from %s\n" (String.length str) filename ;;
The compiler will turn the recursive loop int a real while loop, which runs in constant stack space because of tail recursion and not cause stack overflow.
Yes, we should close the channel:
...
try loop () with
End_of_file -> close_in chan; Buffer.contents buf
Recursion is great for constructing and examining certain types of data structures, like trees:
TODO: Later in utop: readdir_no_ex, string_of_filesystem, loop, read_directory etc.
Recursion Example: Maximum Element in a List
let rec list_max xs =
match xs with
| [] -> failwith "list_max called on empty list"
| [x] -> x
| x :: remainder -> max x (list_max remainder) ;;
list_max [1; 2; 3; 4; 1] ;;
list_max [] ;;
list_max [5; 4; 3; 2; 1];;
list_max [5; 4; 3; 2; 1; 100] ;;
If you want, you can also formally prove that list_max is correct.
Tail Recursion
let rec range a b =
if a > b then []
else a :: range (a + 1) b ;;
Rewrite with:
let rec range a b =
if a > b then [] else
let result = range (a + 1) b in
a :: result ;;
List.length (range 1 10) ;;
List.length (range 1 1000000) ;;
range is not tail-recursive, and hence we can use an accumulator.
let rec range2 a b accum =
if a > b then accum
else range2 (a + 1) b (a :: accum) ;;
let range a b =
List.rev (range2 a b []) ;;
List.length (range 1 1000000) ;;
The following implementation is twice as fast than the previous one because it does not need to reverse a list:
OCaml records:
type pair_of_ints = {a : int; b : int} ;;
{a = 3; b = 5} ;;
{a = 3 } ;;
The record can have a mutable field.
type name = {name : string; mutable access_count : int} ;;
let print_name name =
print_endline ("The name is " ^ name.name);
name.access_count <- name.access_count + 1 ;;
let n = {name = "Richard Jones"; access_count = 0} ;;
n ;;
print_name n ;;
n ;;
print_name n ;;
n ;;
n.name <- "John Smith" ;;
References are implemented using records with a mutable contents field.
The definition at https://v2.ocaml.org/api/Stdlib.html#1_References.
let r = ref 100 ;;
Arrays are another mutable structure.
In OCaml, lists are implemented using linked lists and they can be slow to find the nth element or random access (because of iterating over a list).
The OCaml Array is a real array.
TODO: Array in utop.
If we want two functions that call each other:
let rec even n =
match n with
| 0 -> true
| x -> odd (x - 1) ;;
let rec even n =
match n with
| 0 -> true
| x -> odd (x - 1) ;;
let rec odd n =
match n with
| 0 -> false
| x -> even (x - 1);;
No “forward prototypes” in OCaml (as seen in C). Use a special syntax:
let rec even n =
match n with
| 0 -> true
| x -> odd (x - 1)
and odd n =
match n with
| 0 -> false
| x -> even (x - 1) ;;
even 4 ;;
odd 3 ;;
even 5 ;;
odd 6 ;;
You can write mutually recursive class definitions and modules.
Reference: https://ocaml.org/install
Install opam
$ bash -c "sh <(curl -fsSL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh)"
Review: https://ocaml.org/docs/installing-ocaml
Initialise switch
$ which opam
$ opam switch
$ opam init
Evaluate environment
$ eval $(opam env --switch=default)
$ opam switch
$ opam list
Install OCaml platform tools:
$ opam install ocaml-lsp-server odoc ocamlformat utop
$ opam list
Verify utop
$ utop
Note:
- for Emacs, add these lines to ~/.emacs: (add-to-list ‘load-path ”home/shakthi.opam/default/share/emacs/site-lisp”) (require ‘ocp-indent)
Test read_line () in utop:
read_line ;;
read_line () ;;
To delete:
$ rm -rf .opam
$ rm /usr/local/bin/opam
Reference: https://ocaml.org/docs/opam-switch-introduction
List switches:
$ opam switch list
Help:
$ opam switch --help
List available switches
$ opam switch list-available
Create a new switch:
$ opam switch create 4.14 4.14.2
Change to a specific switch:
$ opam switch 4.14
Confirm the switch:
$ opam switch
$ opam list
See ~/.opam/4.14 and ~/.opam/default.
OCaml comes with two compilers: one translating sources into native binaries and another turning sources into a bytecode format.
OCaml also comes with an interpreter for that bytecode format.
$ opam list | grep dune
$ opam exec -- dune init proj hello
If already ran eval $(opam env), omit “opam exec –” command.
$ dune init proj hello
Install tree command to list files and directories:
$ sudo apt install tree
$ cd hello
$ tree
bin: executable programs lib: libraries test: tests
Dune can be used for:
- running tests
- generate documentation
- produce packaging metadata
- create arbitrary files
The _build folder is where Dune stores all the files it generates.
Build:
$ opam exec -- dune build
Execute:
$ opam exec -- dune exec hello
$ opam exec -- dune build -w
OR
$ opam exec -- dune exec hello -w
There is no requirement that a project must contain a file with that name in order to produce an executable.
An executable OCaml file’s entry point is its first line.
Statements are just processed in order from top to bottom, each triggering the side effects it may have.
Definitions are added to the environment.
A common practice to single out a value that triggers all the side effects and mark it as the intended main entry point.
In OCaml, that’s the role of “let () =”.
A module is a collection of named values.
Identical names from distinct modules don’t clash.
The standard library is collection of several modules.
Try printf from the Printf module in bin/main.ml as fellows:
let () = Printf.printf "%s\n" "Hello, World!"
Each OCaml file defines a module, once compiled.
References to external moduls create dependencies.
Circular dependencies between modules are not allowed.
To create a module, write a new file lib/en.ml:
let v = "Hello, world!"
Update bin/main.ml:
let () = Printf.printf "%s\n" Hello.En.v
Execute:
opam exec -- dune exec hello
Interactive session using utop
$ opam exec -- dune utop
utop # #show Hello.En ;;
module En : sig val v : string end
utop # #quit ;;
If you add a file named hello.ml in the lib folder, Dune will consider this the whole Hello module and it will make En unreachable.
If you want your module En to be visible, you need to add this in your hello.ml file:
module En = En
UTop’s #show command displays an API: the list of definitions provided by a module.
In OCaml, this is called a modular interface.
An .ml file defines a module.
An .mli file defines a module interface. It must have the same base name as the module file.
Example: en.mli is the module interface for module en.ml
Create lib/en.mli:
val v : string
Only the declarations between sig and end from #show output are written.
Module interfaces are also used to create private definitions.
A module definition is private if it is not listed in its correspending module interface.
Update lib/en.ml:
let hello = "Hello"
let v = hello ^ ", world!"
Update bin/main.ml
let hello = "Hello"
let v = hello ^ ", world!"
Try compiling:
$ opam exec -- dune exec hello
This is because we have not changed lib/en.mli.
“hello” is not there in en.mli and it is private.
Multiple modules in a single library.
Create lib/es.ml:
let v = "¡Hola, mundo!"
Update bin/main.ml
let () = Printf.printf "%s\n" Hello.Es.v
let () = Printf.printf "%s\n" Hello.En.v
Execute:
$ opam exec -- dune exec hello
$ opam install dream
$ opam show -f version dream
Update bin/main.ml
let () = Dream.(run (router [ get "/" (fun (_ : request) -> html Hello.En.v) ]))
It responds to HTTP ‘/’ requests with the content of Hello.En.v
Update bin/dune to include the dream library:
(executable
(public_name hello)
(name main)
(libraries hello dream))
Launch the server:
$ opam exec -- dune exec hello
Test:
$ curl localhost:8080
We want to display the output as a list of strings.
$ opam install ppx_show
Update lib/dune:
(library
(name hello)
(preprocess (pps ppx_show))
(libraries ppx_show.runtime))
Update lib/en.mli:
val string_of_string_list : string list -> string
val v : string list
Update lib/en.ml:
let string_list_pp = [%show: string list]
let string_of_string_list = Format.asprintf "@[%a@]" string_list_pp
let v = String.split_on_char ' ' "Hello using an opam library"
v has the type string list. We’re using String.split_on_char to turn a string into a string list by splitting the string on space characters.
string_of_string_list has type string list -> string. This converts a list of strings into a string, applying the expected formatting.
string_list_pp has type Format.formatter -> string list -> unit, which means it is a custom formatter that turns a string list into a string
Edit bin/main.ml:
let () = print_endline Hello.En.(string_of_string_list v)
Execute:
$ opam exec -- dune exec hello
$ opam list | grep dune
$ mkdir minimo
$ cd minimo
$ touch dune-project dune minimo.ml
dune-project:
(lang dune 3.14)
dune
(executable (name minimo))
minimo.ml
let () = print_endline "My name is Minimo"
Note: minimo.exe is not a file name.
This is how Dune is told to compile the minimo.ml file.
An empty file is valid OCaml syntax.
A Tour of OCaml
utop> read_line ;;
utop> read_line () ;;
Loops and Recursion
utop # let quit_loop = ref false in
while not !quit_loop do
print_string "Have you had enough yet? (y/n) ";
let str = read_line () in
if str.[0] = 'y' then quit_loop := true
done ;;
Have you had enough yet? (y/n) n
Have you had enough yet? (y/n) n
Have you had enough yet? (y/n) n
Have you had enough yet? (y/n) y
- : unit = ()
Note: Use Ctrl + l to clear utop output.
Recursion Approach 1
utop # let read_whole_chan chan =
let len = in_channel_length chan in
let result = (Bytes.create len) in
really_input chan result 0 len;
(Bytes.to_string result) ;;
val read_whole_chan : in_channel -> string = <fun>
utop # let read_whole_file filename =
let chan = open_in filename in
read_whole_chan chan ;;
val read_whole_file : string -> string = <fun>
utop # let main () =
let filename = "/etc/resolv.conf" in
let str = read_whole_file filename in
printf "I read %d characters from %s\n" (String.length str) filename ;;
val main : unit -> unit = <fun>
utop # main () ;;
I read 920 characters from /etc/resolv.conf
- : unit = ()
Approach 2
utop # let read_whole_chan chan =
let buf = Buffer.create 4096 in
try
while true do
let line = input_line chan in
Buffer.add_string buf line;
Buffer.add_char buf '\n'
done;
assert false
with
End_of_file -> Buffer.contents buf ;;
val read_whole_chan : in_channel -> string = <fun>
utop # main () ;;
I read 920 characters from /etc/resolv.conf
- : unit = ()
Approach 3
utop # let read_whole_chan chan =
let buf = Buffer.create 4096 in
let rec loop () =
let line = input_line chan in
Buffer.add_string buf line;
Buffer.add_char buf '\n';
loop ()
in
try loop () with
End_of_file -> Buffer.contents buf ;;
val read_whole_chan : in_channel -> string = <fun>
utop # main () ;;
I read 920 characters from /etc/resolv.conf
- : unit = ()
utop # type filesystem = File of string | Directory of filesystem list ;;
type filesystem = File of string | Directory of filesystem list
utop # #use "topfind" ;;
- : unit = ()
Findlib has been successfully loaded. Additional directives:
#require "package";; to load a package
#list;; to list the available packages
#camlp4o;; to load camlp4 (standard syntax)
#camlp4r;; to load camlp4 (revised syntax)
#predicates "p,q,...";; to set these predicates
Topfind.reset();; to force that packages will be reloaded
#thread;; to enable threads
- : unit = ()
utop # #require "unix" ;;
utop # open Unix ;;
utop # let readdir_no_ex dirh =
try
Some (readdir dirh)
with
End_of_file -> None ;;
val readdir_no_ex : dir_handle -> string option = <fun>
utop # readdir_no_ex ;;
- : dir_handle -> string option = <fun>
utop # let rec string_of_filesystem fs =
match fs with
| File filename -> filename ^ "\n"
| Directory fs_list ->
List.fold_left (^) "" (List.map string_of_filesystem fs_list) ;;
val string_of_filesystem : filesystem -> string = <fun>
utop # let rec read_directory path =
let dirh = opendir path in
let rec loop () =
let filename = readdir_no_ex dirh in
match filename with
| None -> []
| Some "." -> loop ()
| Some ".." -> loop ()
| Some filename ->
let pathname = path ^ "/" ^ filename in
let stat = lstat pathname in
let this =
if stat.st_kind = S_DIR then
read_directory pathname
else
File pathname
in
this :: loop ()
in
Directory (loop ()) ;;
val read_directory : string -> filesystem = <fun>
utop # read_directory "~" ;;
Exception: Unix.Unix_error(Unix.ENOENT, "opendir", "~")
utop # read_directory "/etc" ;;
Exception: Unix.Unix_error(Unix.EACCES, "opendir", "/etc/cups/ssl")
utop # read_directory "/home/shakthi/code/twitch" ;;
Array
utop # #use "topfind" ;;
utop # #require "base" ;;
utop # open Base ;;
utop # open Array ;;
utop # let a = Array.create 10 0 ;;
Line 1, characters 8-20:
Warning 6 [labels-omitted]: label len was omitted in the application of this function.
val a : int/2 array = [|0; 0; 0; 0; 0; 0; 0; 0; 0; 0|]
utop # for i = 0 to Array.length a - 1 do
a.(i) <- i
done ;;
- : unit/2 = ()
utop # a ;;
- : int/2 array = [|0; 1; 2; 3; 4; 5; 6; 7; 8; 9|]
Matrix Multiplication
Note: Start with new utop session.
utop # #use "topfind" ;;
- : unit = ()
Findlib has been successfully loaded. Additional directives:
#require "package";; to load a package
#list;; to list the available packages
#camlp4o;; to load camlp4 (standard syntax)
#camlp4r;; to load camlp4 (revised syntax)
#predicates "p,q,...";; to set these predicates
Topfind.reset();; to force that packages will be reloaded
#thread;; to enable threads
- : unit = ()
utop # #require "stdio" ;;
utop # open Stdio;;
utop # let size = 30 ;;
val size : int/2 = 30
utop # let mkmatrix rows cols =
let count = ref 1
and last_col = cols - 1
and m = Array.make_matrix rows cols 0 in
for i = 0 to rows - 1 do
let mi = m.(i) in
for j = 0 to last_col do
mi.(j) <- !count;
incr count
done;
done;
m ;;
Line 4, characters 15-32:
Warning 6 [labels-omitted]: labels dimx, dimy were omitted in the application of this function.
Line 9, characters 15-19:
Alert deprecated: Base.incr
[2016-09] this element comes from the stdlib distributed with OCaml.
Use [Int.incr] instead.
val mkmatrix : int -> int -> int/2 array array = <fun>
utop # let rec inner_loop k v m1i m2 j =
if k < 0 then v
else inner_loop (k - 1) (v + m1i.(k) * m2.(k).(j)) m1i m2 j ;;
val inner_loop : int -> int -> int array -> int array array -> int/2 -> int =
<fun>
utop # let mmult rows cols m1 m2 m3 =
let last_col = cols - 1
and last_row = rows - 1 in
for i = 0 to last_row do
let m1i = m1.(i) and m3i = m3.(i) in
for j = 0 to last_col do
m3i.(j) <- inner_loop last_row 0 m1i m2 j
done;
done ;;
val mmult :
int -> int -> int array array -> int array array -> int array array -> unit/2 =
<fun>
let () =
let n =
try int_of_string "3"
with Invalid_argument _ -> 1
and m1 = mkmatrix size size
and m2 = mkmatrix size size
and m3 = Array.make_matrix size size 0 in
for i = 1 to n - 1 do
mmult size size m1 m2 m3
done;
mmult size size m1 m2 m3;
Printf.printf "%d %d %d %d\n" m3.(0).(0) m3.(2).(3) m3.(3).(2) m3.(4).(4);;
Reference: https://ocaml.org/docs/labels
Give names and default values to function parameters. These are known as labels.
In this tutorial, the parameters that are not labelled are called positional parameters.
utop # Option.value ;;
Labelled arguments are passed using a tilde ~ and can be placed at any position and in any order.
utop # Option.value (Some 10) ~default:42 ;;
- : int = 10
utop # Option.value ~default:42 (Some 10) ;;
- : int = 10
utop # Option.value ~default:42 None ;;
- : int = 42
Passing labelled arguments through the pipe operator will throw a syntax error:
utop # ~default:42 |> Option.value None ;;
Error: Syntax error
utop # let rec range ~first:lo ~last:hi =
if lo > hi then []
else lo :: range ~first:(lo + 1) ~last:hi ;;
val range : first:int -> last:int -> int list = <fun>
utop # range ~first:1 ~last:10 ;;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
utop # range ~last:10 ~first:1 ;;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
The parameters of range are named:
- lo and hi inside the function’s body, as usual
- first and last are labels when calling the function.
You can use a shorter syntax:
utop # let rec range ~first ~last =
if first > last then []
else first :: range ~first:(first + 1) ~last ;;
val range : first:int -> last:int -> int list = <fun>
utop # range ~last:10 ~first:1 ;;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
At parameter definition ~first is the same as ~first:first. Passing argument ~last is the same as ~last:last.
Optional arguments can be omitted.
When passed, a tilde ~ or a question mark ? must be used.
They can be placed at any position and in any order.
utop # let sum ?(init=0) u = List.fold_left ( + ) init u;;
val sum : ?init:int -> int list -> int = <fun>
utop # sum [0; 1; 2; 3; 4; 5];;
- : int = 15
utop # sum [0; 1; 2; 3; 4; 5] ~init:100;;
- : int = 115
It is also possible to pass optional arguments as values of type option.
This is done using a question mark when passing the argument.
utop # sum [0; 1; 2; 3; 4; 5] ?init:(Some 100);;
- : int = 115
utop # sum [0; 1; 2; 3; 4; 5] ?init:None;;
- : int = 15
In the following function, ?init:(x = 0) means that ~init is an optional parameter that defaults to 0. Inside the function, the parameter is named x.
utop # let sum ?init:(x=0) u = List.fold_left ( + ) x u;;
val sum : ?init:int -> int list -> int = <fun>
utop # sum [0; 1; 2; 3; 4; 5] ?init:(Some 100);;
- : int = 115
utop # sum [0; 1; 2; 3; 4; 5] ?init:None;;
- : int = 15
An optional parameter can be declared without specifying a default value.
utop # let sub ?(pos=0) ?len:len_opt s =
let default = String.length s - pos in
let length = Option.value ~default len_opt in
String.sub s pos length;;
val sub : ?pos:int -> ?len:int -> string -> string = <fun>
We are defining a variant of the function String.sub from the standard library:
- s is the string from which we are extracting a substring.
- pos is the substring’s starting position. It defaults to 0.
- len is the substring’s length. If missing, it defaults to String.length s - pos.
When an optional parameter is not given a default value, its type inside the function is made an option.
So, len appears as ?len:int in the function signature. However, inside the body of the function, len_opt is an int option.
utop # sub ~len:5 ~pos:2 "immutability";;
- : string = "mutab"
utop # sub "immutability" ~pos:7 ;;
- : string = "ility"
utop # sub ~len:2 "immutability";;
- : string = "im"
utop # sub "immutability";;
- : string = "immutability"
It is possible to use the same name for the len parameter and label name.
let sub ?(pos=0) ?len s =
let default = String.length s - pos in
let length = Option.value ~default len in
String.sub s pos length;;
utop # sub "immutability";;
- : string = "immutability"
utop # sub ~len:2 "immutability";;
- : string = "im"
utop # sub "immutability" ~pos:7 ;;
- : string = "ility"
utop # sub ~len:5 ~pos:2 "immutability";;
- : string = "mutab"
Let us compare two variants of the String.concat function.
utop # let concat_warn ss ?(sep="") = String.concat sep ss;;
utop # concat_warn ~sep:"--" ["foo"; "bar"; "baz"];;
- : string = "foo--bar--baz"
utop # concat_warn ~sep:"";;
- : string list -> string = <fun>
utop # concat_warn ["foo"; "bar"; "baz"];;
- : ?sep:string -> string = <fun>
In the second version, the optional separator is the first declared parameter:
val concat : ?sep:string -> string list -> string = <fun>
utop # concat ["foo"; "bar"; "baz"] ~sep:"--";;
- : string = "foo--bar--baz"
utop # concat ~sep:"--";;
- : string list -> string = <fun>
utop # concat ["foo"; "bar"; "baz"];;
- : string = "foobarbaz"
Only difference is that when only applied to the argument [“foo”; “bar”; “baz”]. In that case:
- concat returns “foobarbaz”. The default value “” of ~sep is passed.
- concat_warn returns a partially applied function of type ?sep:string -> string. The default value is not passed.
A function’s last declared parameter shouldn’t be optional. The warning suggests turning concat_warn into concat.
Optional parameters make it difficult for the compiler to know if a function is partially applied or not.
This is why at least one positional parameter is required after the optional ones.
If present at application, it means the function is fully applied, if missing, it means the function is partially applied.
Declaring a function’s unlabelled argument as the first one simplifies reading the function’s type and does not prevent passing this argument using the pipe operator.
utop # let rec range step ~first ~last = if first > last then [] else first :: range step ~first:(first + step) ~last;;
val range : int -> first:int -> last:int -> int list = <fun>
utop # 3 |> range ~last:10 ~first:1;;
- : int list = [1; 4; 7; 10]
When all parameters of a function need to be optional, a dummy, positional and occurring last parameter must be added.
The unit () value comes in handy for this.
utop # let hello ?(who="world") () = "hello, " ^ who;;
val hello : ?who:string -> unit -> string = <fun>
utop # hello ;;
- : ?who:string -> unit -> string = <fun>
utop # hello () ;;
- : string = "hello, world"
utop # hello ~who:"sabine" ;;
- : unit -> string = <fun>
utop # hello ~who:"sabine" () ;;
- : string = "hello, sabine"
utop # hello () ?who:None ;;
- : string = "hello, world"
utop # hello ?who:(Some "christine") () ;;
- : string = "hello, christine"
Without the unit parameter, the optional argument cannot be erased warning would be emitted.
Passing an optional argument with a question mark sign ? allows forwarding it without unwrapping.
utop # let take ?len s = sub ?len s;;
val take : ?len:int -> string -> string = <fun>
utop # take "immutability" ~len:2;;
- : string = "im"
utop # let rtake ?off s = sub ?pos:off s;;
val rtake : ?off:int -> string -> string = <fun>
utop # rtake "immutability" ~off:7;;
- : string = "ility"
In the definitions of take and rtake, the function sub is called with optional arguments passed with question marks.
In take the optional argument has the same name as in sub; writing ?len is sufficient to forward without unwrapping.
Functions can have named or optional parameters.
The let name-value inding is immutable.
utop # let d = ref 0 ;;
val d : int ref = {contents = 0}
utop # d ;;
- : int ref = {contents = 0}
utop # d := 1 ;;
- : unit = ()
utop # d ;;
- : int ref = {contents = 1}
utop # !d ;;
- : int = 1
The assignment operator := is just a function. It takes
- the reference to be updated, and
- the value that replaces the previous contents.
utop # ( := ) ;;
- : 'a ref -> 'a -> unit = <fun>
The dereference operator is a function that takes a reference and returns its contents.
utop # ( ! ) ;;
- : 'a ref -> 'a = <fun>
type book = {
series : string;
volume : int;
title : string;
author : string;
mutable stock : int;
};;
A stock inventory example:
let vol_7 = {
series = "Murderbot Diaries";
volume = 7;
title = "System Collapse";
author = "Martha Wells";
stock = 0
};;
When new stock arrives:
utop # vol_7.stock <- vol_7.stock + 10 ;;
utop # vol_7 ;;
Note: There is no special syntax to dereference a mutable record field.
utop # #show_type ref ;;
type 'a ref = { mutable contents : 'a; }
The type ‘a ref is a record with a single field contents, which is marked with the mutable keyword.
We can define functions create, assign, and deref using the mutable record field update syntax:
utop # let create v = { contents = v };;
val create : 'a -> 'a ref = <fun>
utop # let assign f v = f.contents <- v;;
val assign : 'a ref -> 'a -> unit = <fun>
utop # let deref f = f.contents;;
val deref : 'a ref -> 'a = <fun>
utop # let f = create 0 ;;
val f : int ref = {contents = 0}
utop # deref f ;;
- : int = 0
utop # assign f 2 ;;
- : unit = ()
utop # deref f ;;
- : int = 2
The functions:
- create does the same as the ref function provided by the standard library.
- assign does the same as the ( := ) operator.
- deref does the same as the ( ! ) operator.
In OCaml, an array is a mutable, fixed-size data structure that can store a sequence of elements of the same type.
Arrays are indexed by integers, provide constant-time access, and allow the update of elements.
utop # let g = [| 2; 3; 4; 5; 6; 7; 8 |];;
val g : int array = [|2; 3; 4; 5; 6; 7; 8|]
utop # g.(0) ;;
- : int = 2
utop # g.(0) <- 9 ;;
- : unit = ()
utop # g.(0) ;;
- : int = 9
utop # g ;;
- : int array = [|9; 3; 4; 5; 6; 7; 8|]
The bytes type in OCaml represents a fixed-length, mutable byte sequence.
Each element has 8 bits.
bytes values are mutable char sequences.
utop # let h = Bytes.of_string "abcdefghijklmnopqrstuvwxyz";;
val h : bytes = Bytes.of_string "abcdefghijklmnopqrstuvwxyz"
utop # Bytes.get h 10 ;;
- : char = 'k'
utop # Bytes.set h 10 '_' ;;
- : unit = ()
utop # h ;;
- : bytes = Bytes.of_string "abcdefghij_lmnopqrstuvwxyz"
Byte sequences can be created from string values using the function Bytes.of_string.
Note: bytes type uses a much more compact memory representation than char array.
We compare two ways to implement a get_char function.
The function waits until a key is pressed and returns the corresponding character without echoing it.
We use two functions from the Unix module to read and update attributes of the terminal associated with standard input:
tcgetattr stdin TCSAFLUSH returns the terminal attributes as a record (similar to deref)
tcsetattr stdin TCSAFLUSH updates the terminal attributes (similar to assign)
The logic is the same in both implementations:
- Read and record the terminal attributes
- Set the terminal attributes
- Wait until a key is pressed, read it as a character
- Restore the initial terminal attributes
- Return the read character
We read characters from standard input using the input_char function from the OCaml standard library.
utop # #use "topfind" ;;
utop # #require "unix" ;;
utop # let get_char () =
let open Unix in
let termio = tcgetattr stdin in
let c_icanon, c_echo = termio.c_icanon, termio.c_echo in
termio.c_icanon <- false;
termio.c_echo <- false;
tcsetattr stdin TCSAFLUSH termio;
let c = input_char (in_channel_of_descr stdin) in
termio.c_icanon <- c_icanon;
termio.c_echo <- c_echo;
tcsetattr stdin TCSAFLUSH termio;
c;;
val get_char : unit -> char = <fun>
utop # get_char () ;;
- : char = 'a'
In the second implementation, the record returned by the call to tcgetattr is not modified.
A copy is made using { termio with c_icanon = false; c_echo = false }.
utop # let get_char () =
let open Unix in
let termio = tcgetattr stdin in
tcsetattr stdin TCSAFLUSH
{ termio with c_icanon = false; c_echo = false };
let c = input_char (in_channel_of_descr stdin) in
tcsetattr stdin TCSAFLUSH termio;
c;;
val get_char : unit -> char = <fun>
utop # get_char () ;;
- : char = 'b'
Using let … in construct:
- Names may be bound.
- Side effects take place in sequence
utop # let () = print_string "This is" in print_endline " really Disco!";;
This is really Disco!
- : unit = ()
The single semicolon ; operator is known as the sequence operator.
It allows you to evaluate multiple expressions in order.
utop # let _ =
print_endline "Hello,";
print_endline "world!";
42;;
Hello,
world!
- : int = 42
Even though it is called the sequence operator, the semicolon is not truly an operator.
It is rather a construct of the language.
It allows adding a semicolon at the end of a sequence expression.
utop # (); 42; ;;
- : int = 42
The semicolon after 42 is ignored.
What we want to do in order:
- Increment r
- Compute 2 * !r
- Assign into r
Use of begin … end with an example:
utop # let f r = r := incr 2; 2 * !r ;;
Error: This expression has type int but an expression was expected of type
int ref
utop # let f r = r := begin incr r; 2 * !r end ;;
val f : int ref -> unit = <fun>
utop # begin end ;;
- : unit = ()
utop # 6 * if "foo" = "bar" then 5 else 5 + 2;;
- : int = 42
A conditional expression return type can be unit if both branches are too.
utop # if 0 = 1 then print_endline "foo" else print_endline "bar";;
bar
- : unit = ()
The above can also be written as:
utop # print_endline (if 0 = 1 then "foo" else "bar");;
bar
- : unit = ()
utop # if 0 = 1 then print_endline "foo" else ();;
- : unit = ()
Without an else branch:
utop # if 0 = 1 then print_endline "foo";;
- : unit = ()
In parsing, conditional expressions groups more than sequencing:
utop # if true then print_endline "A" else print_endline "B"; print_endline "C";;
A
C
- : unit = ()
If you want to have two prints in a conditional expression branch, use begin … end.
utop # if true then
print_endline "A"
else begin
print_endline "B";
print_endline "C"
end;;
A
- : unit = ()
Failing to group in the first branch results in a syntax error.
utop # if true then
print_endline "A";
print_endline "C"
else
print_endline "B";;
Error: Syntax error
utop # for i = 0 to 5 do Printf.printf "%i\n" i done;;
0
1
2
3
4
5
- : unit = ()
let j = [| 2; 3; 4; 5; 6; 7; 8 |];;
utop # for i = Array.length j - 1 downto 0 do 0 done;;
For loops are convenient to iterate over and modify arrays:
utop # let sum = ref 0 in
for i = 0 to Array.length j - 1 do sum := !sum + j.(i) done;
!sum;;
- : int = 35
The same using an iterator function:
let sum = ref 0 in Array.iter (fun i -> sum := !sum + i) j; !sum;;
utop # let sum = ref 0 in Array.iter (fun i -> sum := !sum + i) j; !sum;;
- : int = 35
A while loop is an expression of type unit:
utop # let i = ref 0 in
while !i <= 5 do
Printf.printf "%i\n" !i;
i := !i + 1;
done;;
0
1
2
3
4
5
- : unit = ()
Throwing the Exit exception is a recommended way to immediately exit from a loop.
utop # try
print_endline "Press Escape to exit";
while true do
let c = get_char () in
if c = '\027' then raise Exit;
print_char c;
flush stdout
done
with Exit -> ();;
Press Escape to exit
- : unit = ()
The function create_counter returns a closure that hides a mutable reference n.
utop # let create_counter () =
let n = ref 0 in
fun () -> incr n; !n;;
val create_counter : unit -> unit -> int = <fun>
This reference will hold the state of the counter.
Next, we define a closure that takes no arguments (fun () ->).
The closure increments the value of n (the counter) using incr n, then returns the current value of n using !n.
utop # let c1 = create_counter ();;
val c1 : unit -> int = <fun>
utop # let c2 = create_counter ();;
val c2 : unit -> int = <fun>
utop # c1 () ;;
- : int = 1
utop # c1 () ;;
- : int = 2
utop # c2 () ;;
- : int = 1
utop # c1 () ;;
- : int = 3
We show some patterns and anti-patterns relating to mutable states and side effects:
No mutability is exposed.
utop # let sum m =
let result = ref 0 in
for i = 0 to Array.length m - 1 do
result := !result + m.(i)
done;
!result;;
val sum : int array -> int = <fun>
It is a fully encapsulated implementation choice.
This function is safe to use.
Some applications maintain state:
- REPL
- Server for a stateful protocol (session has state).
- Text editor
- Cache
A toy line editor example.
It waits for characters on standard input and exits on end-of-file, carriage return, or newline.
If the character is printable, it prints it and records it in a mutable list used as a stack.
If the character is the delete code, the stack is popped and the last printed character is erased.
utop # #use "topfind" ;;
utop # #require "unix" ;;
utop # let get_char () =
let open Unix in
let termio = tcgetattr stdin in
tcsetattr stdin TCSAFLUSH
{ termio with c_icanon = false; c_echo = false };
let c = input_char (in_channel_of_descr stdin) in
tcsetattr stdin TCSAFLUSH termio;
c;;
val get_char : unit -> char = <fun>
utop # let record_char state c =
(String.make 1 c, c :: state);;
val record_char : char list -> char -> string * char list = <fun>
utop # let remove_char state =
("\b \b", if state = [] then [] else List.tl state);;
val remove_char : 'a list -> string * 'a list = <fun>
utop # let state_to_string state =
List.(state |> rev |> to_seq |> String.of_seq);;
val state_to_string : char list -> string = <fun>
utop # let rec loop state =
let c = get_char () in
if c = '\004' || c = '\n' || c = '\r' then raise Exit;
let s, new_state = match c with
| '\127' -> remove_char !state
| c when c >= ' ' -> record_char !state c
| _ -> ("", !state) in
print_string s;
state := new_state;
flush stdout;
loop state;;
val loop : char list ref -> 'a = <fun>
utop # let state = ref [] in try loop state with Exit -> state_to_string !state;;
a- : string = "a"
The functions record_char and remove_char neither update the state nor produce side effects.
They each return a pair of values consisting of a string to print and the next state, new_state.
I/O and state-update side effects happen inside the loop function.
The state is passed as argument to the loop function.
state-aware code is contained in a narrow scope; the rest of the code is purely functional.
Note: The state is copied, which is not memory efficient. In a memory-aware implementation, state-update functions would produce a “diff”.
Consider store angles as fractions of the circle in 8-bit unsigned integers, storing them as char values.
So, 64 is 90 degrees, 128 is 180 degrees, 192 is 270 degrees, 256 is full circle, and so on.
Compute cosine as follows:
utop # let char_cos c =
c |> int_of_char |> float_of_int |> ( *. ) (Float.pi /. 128.0) |> cos;;
val char_cos : char -> float = <fun>
utop # char_cos 'c' ;;
- : float = -0.757208846506484567
Make a faster implementation by precomputing all the possible values in advance.
There are only 256 of them:
utop # let char_cos_tab = Array.init 256 (fun i -> i |> char_of_int |> char_cos);;
utop # let char_cos c = char_cos_tab.(int_of_char c);;
val char_cos : char -> float = <fun>
utop # char_cos 'c' ;;
- : float = -0.757208846506484567
Memoization uses a cache that is populated when calling the function.
The provided arguments:
- are found in the cache (it is a hit) and the stored result is returned, or they
- are not found in the cache (it’s a miss), and the result is computed, stored in the cache, and returned.
OCaml programs should be written in a mostly functional style.
Avoid side effects where possible and relying on immutable data instead of mutable state.
A module may expose or encapsulate a state in several different ways:
Good: expose a type representing a state, with state creation or reset functions
It depends: only expose state initialisation, which implies there only is a single state
Bad: mutable state with no explicit initialisation function or no name referring to the mutable state
Good example: Hashtbl module.
Hashtbl.t represents the mutable data.
Exposes create, clear and reset functions.
The clear and reset functions return unit.
This strongly signals the reader that they perform the side-effect of updating the mutable data.
utop # #show Hashtbl.t ;;
type ('a, 'b) t = ('a, 'b) Hashtbl.t
utop # Hashtbl.create ;;
- : ?random:bool -> int -> ('a, 'b) Hashtbl.t = <fun>
utop # Hashtbl.reset ;;
- : ('a, 'b) Hashtbl.t -> unit = <fun>
utop # Hashtbl.clear ;;
- : ('a, 'b) Hashtbl.t -> unit = <fun>
A module may define mutable data internally impacting its behaviour without exposing it in its interface. This is not advisable.
utop # let partition p k =
let m = Array.copy k in
let k_len = ref 0 in
let m_len = ref 0 in
for i = 0 to Array.length k - 1 do
if p k.(i) then begin
k.(!k_len) <- k.(i);
incr k_len
end else begin
m.(!m_len) <- k.(i);
incr m_len
end
done;
(Array.truncate k_len k, Array.truncate m_len m);;
Error: Unbound value Array.truncate
Array.truncate is not defined.
Array.truncate 3 [5; 6; 7; 8; 9] will return [5; 6; 7].
So, the input array is modified.
This function has a side effect that is either:
- not intended, or
- not documented.
utop # module Array = struct
include Stdlib.Array
let copy a =
if Array.length a > 1000000 then Analytics.collect "Array.copy" a;
copy a
end;;
Error: Unbound module Analytics
Analytics.collect is a function that makes a network connection to transmit data to a remote server.
Give the function a descriptive name (for instance, Array.copy_with_analytics) and document the fact that there’s a side-effect that the caller may not be aware of.
utop # let id_print s = print_string (s ^ " "); s;;
val id_print : string -> string = <fun>
utop # let s =
Printf.sprintf "%s %s %s"
(id_print "Monday")
(id_print "Tuesday")
(id_print "Wednesday");;
Wednesday Tuesday Monday val s : string = "Monday Tuesday Wednesday"
The evaluation order for function arguments in OCaml is not explicitly defined, the order in which the id_print side effects take place is unreliable.
In this example, the arguments are evaluated from right to left, but this could change in future compiler releases.
This also arises when applying arguments to variant constructors, building tuple values, or initialising record fields.
utop # let r = ref 0 in ((incr r; !r), (decr r; !r));;
- : int * int = (0, -1)
The value of this expression depends on the order of subexpression evaluation.
Since this order is not specified, there is no reliable way to know what this value is.
At the time of writing this tutorial, the evaluation produced (0, -1), but if you see something else, it is not a bug.
Such an unreliable value must a avoided.
Reference: https://ocaml.org/docs/options
An option value wraps another value or contains nothing if there isn’t anything to wrap.
utop # #show option ;;
type 'a option = None | Some of 'a
utop # Some 42 ;;
- : int option = Some 42
utop # None ;;
- : 'a option = None
The option type is useful when the lack of data is better handled as the special value None rather than an exception.
It is the type-safe version of returning error values.
No confusion between regular values and the absence of a value.
Sys.getenv : string -> string from the OCaml standard library queries the value of an environment variable; however, it throws an exception if the variable is not defined.
utop # Sys.getenv "UNDEFINED_ENVIRONMENT_VARIABLE" ;;
Exception: Not_found.
Sys.getenv_opt : string -> string option does the same, except it returns None if the variable is not defined.
utop # Sys.getenv_opt "UNDEFINED_ENVIRONMENT_VARIABLE" ;;
- : string option = None
Most of the functions are provided by Stdlib.Option module.
Using pattern matching, you can map over an option.
utop # Option.map ;;
- : ('a -> 'b) -> 'a option -> 'b option = <fun>
utop # Option.map (fun x -> x * x) (Some 3);;
- : int option = Some 9
utop # Option.map (fun x -> x * x) None;;
- : int option = None
join peels off one layer from a doubly wrapped option:
utop # Option.join ;;
- : 'a option option -> 'a option = <fun>
utop # Option.join (Some (Some 42));;
- : int option = Some 42
utop # Option.join (Some None);;
- : 'a option = None
utop # Option.join None;;
- : 'a option = None
The function get of type ‘a option -> ‘a allows access to the value contained inside an option.
get o throws an exception if o is None.
To access the content of an option without the risk of raising an exception, the function value of type ‘a option -> ‘a -> ‘a can be used:
utop # Option.get ;;
- : 'a option -> 'a = <fun>
utop # Option.get (Some 42) ;;
- : int = 42
Option.value requires a default value as an additional parameter.
utop # Option.value ;;
- : 'a option -> default:'a -> 'a = <fun>
utop # Option.value (Some 42) ~default:0 ;;
- : int = 42
utop # Option.fold ;;
- : none:'a -> some:('b -> 'a) -> 'b option -> 'a = <fun>
A function that turns the contents of the $PATH environment variable into a list of strings, or the empty list if undefined. This version uses pattern matching:
utop # let path () =
let split_on_colon = String.split_on_char ':' in
let opt = Sys.getenv_opt "PATH" in
match opt with
| Some s -> split_on_colon s
| None -> [];;
val path : unit -> string list = <fun>
utop # path () ;;
- : string list =
["/home/shakthi/.opam/default/bin"; "/home/shakthi/.local/bin";
"/usr/local/sbin"; "/usr/local/bin"; "/usr/sbin"; "/usr/bin"; "/sbin";
"/bin"; "/usr/games"; "/usr/local/games"; "/snap/bin"; "/snap/bin"]
The Option.fold function can be used to implement a fall-back logic without writing pattern matching. Example:
utop # let path () =
let split_on_colon = String.split_on_char ':' in
Sys.getenv_opt "PATH" |> Option.fold ~some:split_on_colon ~none:[];;
val path : unit -> string list = <fun>
utop # path () ;;
- : string list =
["/home/shakthi/.opam/default/bin"; "/home/shakthi/.local/bin";
"/usr/local/sbin"; "/usr/local/bin"; "/usr/sbin"; "/usr/bin"; "/sbin";
"/bin"; "/usr/games"; "/usr/local/games"; "/snap/bin"; "/snap/bin"]
The bind function of type ‘a option -> (‘a -> ‘b option) -> ‘b option works a bit like map.
But whilst map expects a function parameter f that returns an unwrapped value of type b, bind expects an f that returns a value already wrapped in an option ‘b option.
Here, we display the type of Option.map, with parameters flipped and show a possible implementation of Option.bind.
utop # Option.map ;;
- : ('a -> 'b) -> 'a option -> 'b option = <fun>
utop # let bind o f = o |> Option.map f |> Option.join;;
val bind : 'a option -> ('a -> 'b option) -> 'b option = <fun>
Reference: https://ocaml.org/docs/arrays
Arrays are collections of elements of a single type.
Arrays can be mutated. They cannot be resized.
They are efficient to access elements at any position.
Arrays are commonly used in OCaml for tasks such as:
- Storing and processing large amounts of data.
- Implementing algorithms that require random access and modification of elements.
- Working with matrices and other multi-dimensional data structures.
utop # [| 1; 2; 3; 4; 5 |] ;;
- : int array = [|1; 2; 3; 4; 5|]
Array.make function, which takes two arguments: the length of the array and the initial value of each element.
utop # Array.make ;;
- : int -> 'a -> 'a array = <fun>
utop # let zeroes = Array.make 5 0;;
val zeroes : int array = [|0; 0; 0; 0; 0|]
Array.init generates an array of a given length by applying a function to each index of the array, starting at 0.
Example: An array containing the first 5 even numbers using a function which doubles its argument:
utop # let even_numbers = Array.init 5 (fun i -> i * 2);;
val even_numbers : int array = [|0; 2; 4; 6; 8|]
You can access individual elements of an array using the .(index) syntax.
utop # even_numbers.(2);;
- : int = 4
Assign a new value to an Array using the indexing operator:
utop # even_numbers.(2) <- 42;;
- : unit = ()
utop # even_numbers ;;
- : int array = [|0; 2; 42; 6; 8|]
Note: This operation returns unit, and not the modified array.
Array.length function returns the size of an array:
utop # Array.length even_numbers;;
- : int = 5
Array.iter applies a function to each element of an array, one at a time.
The given function must return unit, operating by side effect.
utop # Array.iter (fun x -> print_int x; print_string " ") zeroes;;
0 0 0 0 0 - : unit = ()
Iterating on arrays can also be made using for loops:
utop # for i = 0 to Array.length zeroes - 1 do
print_int zeroes.(i);
print_string " "
done;;
0 0 0 0 0 - : unit = ()
Array.map function creates a new array by applying a given function to each element of an array.
utop # Array.map (fun x -> x * x) even_numbers;;
- : int array = [|0; 4; 1764; 36; 64|]
To combine all the elements of an array into a single result, we can use the Array.fold_left and Array.fold_right functions.
These functions take a binary function, an initial accumulator value, and an array as arguments.
The binary function takes two arguments: the accumulator’s current value and the current element of the array, then returns a new accumulator value.
Both functions traverse the array but in opposite directions.
utop # Array.fold_left ;;
- : ('acc -> 'a -> 'acc) -> 'acc -> 'a array -> 'acc = <fun>
utop # Array.fold_right ;;
- : ('a -> 'acc -> 'acc) -> 'a array -> 'acc -> 'acc = <fun>
To find the maximum element of an array:
utop # Array.fold_left Int.max min_int even_numbers;;
- : int = 42
To sort an array, we can use the Array.sort function. This function takes as arguments:
- A comparison function.
- An array, which is sorted in place and in ascending order, according to the provided comparison function.
Sorting performed by Array.sort modifies the content of the provided array, which is why it returns unit.
utop # Array.sort compare even_numbers;;
- : unit = ()
utop # even_numbers ;;
- : int array = [|0; 2; 6; 8; 42|]
The Array.blit function efficiently copies a contiguous part of an array into an array.
This function modifies the destination in place and returns unit, not the modified array.
utop # let ones = Array.make 5 1;;
val ones : int array = [|1; 1; 1; 1; 1|]
utop # ones ;;
- : int array = [|1; 1; 1; 1; 1|]
utop # Array.blit ones 0 zeroes 1 2;;
- : unit = ()
utop # zeroes ;;
- : int array = [|0; 1; 1; 0; 0|]
utop # ones ;;
- : int array = [|1; 1; 1; 1; 1|]
This copies two elements of ones, starting at index 0 (this array slice is [| 1; 1 |]) into zeroes, starting at index 1.
It is your responsibility to make sure that the two indices provided are valid in their respective arrays and that the number of elements to copy is within the bounds of each array.
You can also use this function to copy part of an array onto itself:
utop # zeroes ;;
- : int array = [|0; 1; 1; 0; 0|]
utop # Array.blit ;;
- : 'a array -> int -> 'a array -> int -> int -> unit = <fun>
utop # Array.blit zeroes 1 zeroes 3 2;;
- : unit = ()
utop # zeroes ;;
- : int array = [|0; 1; 1; 1; 1|]
This copies two elements of zeroes, starting at index 1 into the last part of zeroes, starting at index 3.
Reference:
Sequences are very much like lists.
They may be infinite.
Examples:
- A stream of incoming requests in a server,
- readings from an embedded sensor,
- or system logs.
All have unforeseeable termination, and it is easier to consider them potentially infinite.
The sequence elements are computed on demand and not stored in memory.
They allow for reducing memory consumption from linear to constant space.
Look at a value of type ‘a Seq.t as a list, but when it is not empty: its tail is frozen.
A mutually recursive definition of Seq.node (a sequence) in the standard libray:
type 'a node =
| Nil
| Cons of 'a * 'a t
and 'a t = unit -> 'a node
Similar to the list definition:
type 'a list =
| []
| (::) of 'a * 'a list
The main difference is that Seq.Cons second component’s type, which is a function returning a sequence while its list counterpart is a list.
Comparison:
- Empty lists and sequences are defined the same way, a constructor without any parameters: Seq.Nil and [].
- Non-empty lists and sequences are both pairs whose former member is a piece of data.
- In lists, it is recursively a list, while in sequences, it is a function returning a Seq.node.
A value of type Seq.t is “frozen” because the data it contains isn’t immediately available.
A unit value has to be supplied to recover it, which we may see as “unfreezing.”
However, unfreezing only gives access to the tip of the sequence, since the second argument of Seq.Cons is a function too.
Frozen-by-function tails explain why sequences may be considered potentially infinite.
In OCaml, any value a of type t can be turned into a constant function by writing fun _ -> a or fun () -> a.
The latter function is called a thunk.
Using this terminology, Seq.t values are thunks. With the analogy used earlier, a is frozen in its thunk.
utop # let rec ints n : int Seq.t = fun () -> Seq.Cons (n, ints (n + 1));;
val ints : int -> int Seq.t = <fun>
The function ints n looks as if building the infinite sequence (n; n + 1; n + 2; n + 3;…).
In reality, since machine integers have bounds, the sequence isn’t indefinitely increasing.
When reaching max_int, it will circle down to min_int.
utop # max_int ;;
- : int = 4611686018427387903
utop # min_int ;;
- : int = -4611686018427387904
The OCaml standard library contains a module on sequences called Seq.
It contains a Seq.iter function, which has the same behaviour as List.iter.
Seq.iter print_int (ints 0);;
Ctrl+c to interrupt the ekeuction.
utop # Seq.iter ignore (ints 0);;
The key point is: it doesn’t leak memory.
This example is running in constant space.
It is effectively nothing more than an infinite loop, which can be confirmed by monitoring the space consumption of the program and by noticing that it spins forever without crashing.
Whereas a version of this with a list let rec ints n = n :: ints (n + 1) would allocate a list of length proportional to the running time, and thus would crash by running out of memory pretty quickly.
utop # let rec ints n = n :: ints (n + 1) ;;
val ints : int -> int list = <fun>
utop # ints 0 ;;
Stack overflow during evaluation (looping recursion?).
The Seq module of the OCaml standard library contains the definition of the function Seq.take, which returns a specified number of elements from the beginning of a sequence. Here is a simplified implementation:
let rec take n seq () =
if n <= 0 then
Seq.Nil
else
match seq () with
| Seq.Cons (x, seq) -> Seq.Cons (x, take (n - 1) seq)
| _ -> Seq.Nil
take n seq returns, at most, the n first elements of the sequence seq.
If seq contains less than n elements, an identical sequence is returned.
In particular, if seq is empty, or n is negative, an empty sequence is returned.
The first line of take is a common pattern for recursive functions over sequences.
The last two parameters are:
- a sequence called seq
- a unit value
The function begins by unfreezing seq (that is, calling seq ()) and then pattern matching to look inside the data made available.
However, this does not happen unless a unit parameter is passed to take.
Writing take 10 seq does not compute anything.
It is a partial application and returns a function needing a unit to produce a result.
This can be used to print integers without looping forever:
utop # Seq.ints 0 |> Seq.take 43 |> List.of_seq;;
- : int list =
[0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20; 21;
22; 23; 24; 25; 26; 27; 28; 29; 30; 31; 32; 33; 34; 35; 36; 37; 38; 39; 40;
41; 42]
The Seq module also has a function Seq.filter:
utop # Seq.filter ;;
- : ('a -> bool) -> 'a Seq.t -> 'a Seq.t = <fun>
It will build a sequence of elements satisfying a condition.
Using the trial division algorithm, we can define a function which generates the list of all primes numbers:
let rec trial_div seq () = match seq () with
| Seq.Cons (m, seq) -> Seq.Cons (m, trial_div (Seq.filter (fun n -> n mod m > 0) seq))
| seq -> seq
let primes = Seq.ints 2 |> trial_div;;
val trial_div : int Seq.t -> int Seq.t = <fun>
val primes : int Seq.t = <fun>
utop # primes |> Seq.take 100 |> List.of_seq;;
- : int list =
[2; 3; 5; 7; 11; 13; 17; 19; 23; 29; 31; 37; 41; 43; 47; 53; 59; 61; 67; 71;
73; 79; 83; 89; 97; 101; 103; 107; 109; 113; 127; 131; 137; 139; 149; 151;
157; 163; 167; 173; 179; 181; 191; 193; 197; 199; 211; 223; 227; 229; 233;
239; 241; 251; 257; 263; 269; 271; 277; 281; 283; 293; 307; 311; 313; 317;
331; 337; 347; 349; 353; 359; 367; 373; 379; 383; 389; 397; 401; 409; 419;
421; 431; 433; 439; 443; 449; 457; 461; 463; 467; 479; 487; 491; 499; 503;
509; 521; 523; 541]
trial_div is sometimes called corecursive. Although, it may not terminate, it can indefinitely produce valid output.
Standard higher-order iteration functions are available on sequences:
- Seq.iter
- Seq.map
- Seq.fold_left
There is no fold_right function.
Since OCaml 4.11, there is something which isn’t (yet) available on other types: unfold. Here is how it is implemented:
utop # let rec unfold f x () = match f x with
| None -> Seq.Nil
| Some (x, seq) -> Seq.Cons (x, unfold f seq) ;;
val unfold : ('a -> ('b * 'a) option) -> 'a -> 'b Seq.t = <fun>
Seq.unfold does not have a sequence parameter, but a sequence result.
unfold provides a general means to build sequences.
The result returned by Seq.unfold f x is the sequence built by accumulating the results of successive calls to f until it returns None.
Seq.ints can be implemented using Seq.unfold in a fairly compact way:
utop # let ints = Seq.unfold (fun n -> Some (n, n + 1));;
val ints : int -> int Seq.t = <fun>
map over sequences can be implemented using Seq.unfold. Here is how to write it:
utop # let map f = Seq.unfold (fun seq -> seq |> Seq.uncons |> Option.map (fun (x, seq) -> (f x, seq)));;
val map : ('a -> 'b) -> 'a Seq.t -> 'b Seq.t = <fun>
utop # Seq.ints 0 |> map (fun x -> x * x) |> Seq.take 10 |> List.of_seq;;
- : int list = [0; 1; 4; 9; 16; 25; 36; 49; 64; 81]
The function Seq.uncons returns the head and tail of a sequence if it is not empty. Otherwise, it returns None.
utop # let input_line_opt chan =
try Some (input_line chan, chan)
with End_of_file -> None ;;
val input_line_opt : in_channel -> (string * in_channel) option = <fun>
utop # let cin = open_in "/etc/resolv.conf" in
cin |> Seq.unfold input_line_opt |> Seq.iter print_endline;
close_in cin ;;
# This is /run/systemd/resolve/stub-resolv.conf managed by man:systemd-resolved(8).
# Do not edit.
#
# This file might be symlinked as /etc/resolv.conf. If you're looking at
# /etc/resolv.conf and seeing this text, you have followed the symlink.
#
# This is a dynamic resolv.conf file for connecting local clients to the
# internal DNS stub resolver of systemd-resolved. This file lists all
# configured search domains.
#
# Run "resolvectl status" to see details about the uplink DNS servers
# currently in use.
#
# Third party programs should typically not access this file directly, but only
# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a
# different way, replace this symlink by a static file or a different symlink.
#
# See man:systemd-resolved.service(8) for details about the supported modes of
# operation for /etc/resolv.conf.
nameserver 127.0.0.53
options edns0 trust-ad
search .
- : unit = ()
The Seq module contains this definition:
val cons : 'a -> 'a Seq.t -> 'a Seq.t
Seq.cons is a function and Seq.Cons is a variant’s constructor.
If we define the Fibonacci sequence:
utop # let rec fibs m n = Seq.cons m (fibs n (n + m));;
val fibs : int -> int -> int Seq.t = <fun>
utop # fibs 0 1 ;;
Stack overflow during evaluation (looping recursion?).
It is an unending recursion which blows away the stack.
Instead, use Seq.Cons:
utop # let rec fibs m n () = Seq.Cons (m, fibs n (n + m));;
val fibs : int -> int -> int Seq.t = <fun>
utop # fibs 0 1 |> Seq.take 10 |> List.of_seq;;
- : int list = [0; 1; 1; 2; 3; 5; 8; 13; 21; 34]
The difference lies in the recursive call fibs n (n + m).
In the former definition, the application is complete because fibs is provided with all the arguments it expects.
In the latter definition, the application is partial because the () argument is missing.
Since evaluation is eager in OCaml, in the former case, evaluation of the recursive call is triggered and a non-terminating looping occurs.
In contrast, in the latter case, the partially applied function is immediately returned as a closure.
Sequences are functions as stated by their type:
utop # #show Seq.t ;;
type 'a t = unit -> 'a Seq.node
Functions working with sequences must be written accordingly:
Sequence consumer: partially applied function parameter Sequence producer: partially applied function result
Throughout the standard library, sequences are used as a bridge to perform conversions between many datatypes.
Lists
val List.of_seq : 'a list -> 'a Seq.t
val List.to_seq : 'a Seq.t -> 'a list
Arrays
val Array.of_seq : 'a array -> 'a Seq.t
val Array.to_seq : 'a Seq.t -> 'a array
Strings
val String.of_seq : string -> char Seq.t
val String.to_seq : char Seq.t -> string
Similar functions are also provided for sets, maps, hash tables (Hashtbl), and others.
When implementing a datatype module, it is advised to expose to_seq and of_seq functions.
utop # let range i = List.init i succ ;;
val range : int -> int list = <fun>
utop # range 10 ;;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
utop # range 10 |> List.to_seq ;;
- : int Seq.t = <fun>
In OCaml, every piece of code is wrapped into a module.
Optionally, a module itself can be a submodule of another module.
Create a demo directory with the following files:
athens.ml:
let hello () = print_endline "Hello from Athens"
berlin.ml:
let () = Athens.hello ()
dune-project:
(lang dune 3.7)
dune:
(executable (name berlin))
Build and run using:
$ opam exec -- dune build
$ opam exec -- dune exec ./berlin.exe
Hello from Athens
You can open a module inside a definition, using the let open … in construct:
utop # let list_sum_sq m =
let open List in
init m Fun.id |> map (fun i -> i * i) |> fold_left ( + ) 0;;
val list_sum_sq : int -> int = <fun>
The module access notation can be applied to an entire expression:
utop # let array_sum_sq m =
Array.(init m Fun.id |> map (fun i -> i * i) |> fold_left ( + ) 0);;
val array_sum_sq : int -> int = <fun>
Copy athens.ml into cairo.ml as follows:
let message = "Hello from Cairo"
let hello () = print_endline message
cairo.mli:
val hello : unit -> unit
(** [hello()] displays a greeting message. *)
Update dune:
(executables (names berlin delhi))
Execute:
$ opam exec -- dune exec ./berlin.exe
Hello from Athens
$ opam exec -- dune exec ./delhi.exe
Hello from Cairo
Create exeter.mli:
type aleph = Ada | Alan | Alonzo
type gimel
val gimel_of_bool : bool -> gimel
val gimel_flip : gimel -> gimel
val gimel_to_string : gimel -> string
type dalet = private Dennis of int | Donald of string | Dorothy
val dalet_of : (int, string) Either.t option -> dalet
Create exeter.ml:
type aleph = Ada | Alan | Alonzo
type bet = bool
type gimel = Christos | Christine
let gimel_of_bool b = if (b : bet) then Christos else Christine
let gimel_flip = function Christos -> Christine | Christine -> Christos
let gimel_to_string x = "Christ" ^ match x with Christos -> "os" | _ -> "ine"
type dalet = Dennis of int | Donald of string | Dorothy
let dalet_of = function
| None -> Dorothy
| Some (Either.Left x) -> Dennis x
| Some (Either.Right x) -> Donald x
Update dune:
(executables (names berlin delhi) (modules berlin delhi))
(library (name exeter) (modules exeter) (modes byte))
Build, and run utop:
$ opam exec -- dune utop
Examples:
utop # open Exeter ;;
utop # #show aleph ;;
type aleph = Ada | Alan | Alonzo
utop # #show bet ;;
Unknown element.
utop # #show gime1 ;;
Unknown element.
utop # #show gimel ;;
type gimel
utop # Christos ;;
Error: Unbound constructor Christos
utop # #show_val gimel_of_bool ;;
val gimel_of_bool : bool -> gimel
utop # true |> gimel_of_bool |> gimel_to_string ;;
- : string = "Christos"
utop # true |> gimel_of_bool |> gimel_flip |> gimel_to_string;;
- : string = "Christine"
utop # #show dalet;;
type dalet = private Dennis of int | Donald of string | Dorothy
utop # Donald 42;;
Error: This expression has type int but an expression was expected of type
string
utop # dalet_of (Some (Either.Left 10));;
- : dalet = Dennis 10
utop # let dalet_to_string = function
| Dorothy -> "Dorothy"
| Dennis _ -> "Dennis"
| Donald _ -> "Donald";;
val dalet_to_string : dalet -> string = <fun>
Create florence.ml:
module Hello = struct
let message = "Hello from Florence"
let print () = print_endline message
end
let print_goodbye () = print_endline "Goodbye"
Create glasgow.ml:
let () =
Florence.Hello.print ();
Florence.print_goodbye ()
Adding signatures in florence.ml module:
module Hello : sig
val print : unit -> unit
end = struct
let message = "Hello"
let print () = print_endline message
end
let print_goodbye () = print_endline "Goodbye"
Another way of writing florence.ml:
module HelloType : sig
val print : unit -> unit
end
module Hello : HelloType = struct
let message = "Hello from Florence"
let print () = print_endline message
end
let print_goodbye () = print_endline "Goodbye"
See the contents of an existing Unit module:
utop # #show Unit ;;
module Unit :
sig
type t = unit = ()
val equal : t -> t -> bool
val compare : t -> t -> int
val to_string : t -> string
end
You can dump an .ml file’s default interface:
$ ocamlc -c -i cairo.ml
val message : string
val hello : unit -> unit
Create extlib.ml:
module List = struct
include Stdlib.List
let uncons = function
| [] -> None
| hd :: tl -> Some (hd, tl)
end
utop # let s = Random.get_state () ;;
val s : Random.State.t = <abstr>
utop # Random.bits () ;;
- : int = 691769754
utop # Random.bits () ;;
- : int = 1061553262
utop # Random.set_state s ;;
- : unit = ()
utop # Random.bits () ;;
- : int = 691769754
utop # module Array = struct
include Stdlib.Array
include ScanLeft.Make(Stdlib.Array)
end;;
module Array :
sig
type 'a t = 'a array
external length : 'a t -> int = "%array_length"
external get : 'a t -> int -> 'a = "%array_safe_get"
external set : 'a t -> int -> 'a -> unit = "%array_safe_set"
external make : int -> 'a -> 'a t = "caml_make_vect"
external create_float : int -> float t = "caml_make_float_vect"
val init : int -> (int -> 'a) -> 'a t
val make_matrix : int -> int -> 'a -> 'a t t
val append : 'a t -> 'a t -> 'a t
val concat : 'a t list -> 'a t
val sub : 'a t -> int -> int -> 'a t
val copy : 'a t -> 'a t
val fill : 'a t -> int -> int -> 'a -> unit
val blit : 'a t -> int -> 'a t -> int -> int -> unit
val to_list : 'a t -> 'a list
val of_list : 'a list -> 'a t
val iter : ('a -> unit) -> 'a t -> unit
val iteri : (int -> 'a -> unit) -> 'a t -> unit
val map : ('a -> 'b) -> 'a t -> 'b t
val map_inplace : ('a -> 'a) -> 'a t -> unit
val mapi : (int -> 'a -> 'b) -> 'a t -> 'b t
val mapi_inplace : (int -> 'a -> 'a) -> 'a t -> unit
val fold_left : ('acc -> 'a -> 'acc) -> 'acc -> 'a t -> 'acc
val fold_left_map :
('acc -> 'a -> 'acc * 'b) -> 'acc -> 'a t -> 'acc * 'b t
val fold_right : ('a -> 'acc -> 'acc) -> 'a t -> 'acc -> 'acc
val iter2 : ('a -> 'b -> unit) -> 'a t -> 'b t -> unit
val map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
val for_all : ('a -> bool) -> 'a t -> bool
val exists : ('a -> bool) -> 'a t -> bool
val for_all2 : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool
val exists2 : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool
val mem : 'a -> 'a t -> bool
val memq : 'a -> 'a t -> bool
val find_opt : ('a -> bool) -> 'a t -> 'a option
val find_index : ('a -> bool) -> 'a t -> int option
val find_map : ('a -> 'b option) -> 'a t -> 'b option
val find_mapi : (int -> 'a -> 'b option) -> 'a t -> 'b option
val split : ('a * 'b) t -> 'a t * 'b t
val combine : 'a t -> 'b t -> ('a * 'b) t
val sort : ('a -> 'a -> int) -> 'a t -> unit
val stable_sort : ('a -> 'a -> int) -> 'a t -> unit
val fast_sort : ('a -> 'a -> int) -> 'a t -> unit
val to_seq : 'a t -> 'a Seq.t
val to_seqi : 'a t -> (int * 'a) Seq.t
val of_seq : 'a Seq.t -> 'a t
external unsafe_get : 'a t -> int -> 'a = "%array_unsafe_get"
external unsafe_set : 'a t -> int -> 'a -> unit = "%array_unsafe_set"
module Floatarray = Array.Floatarray
val scan_left : ('b -> 'a -> 'b) -> 'b -> 'a t -> 'b t
end
utop # module List = struct
include List
include ScanLeft.Make(struct
include List
let of_list = Fun.id
end)
end;;
module List :
sig
type 'a t = 'a list = [] | (::) of 'a * 'a list
val length : 'a t -> int
val compare_lengths : 'a t -> 'b t -> int
val compare_length_with : 'a t -> int -> int
val is_empty : 'a t -> bool
val cons : 'a -> 'a t -> 'a t
val hd : 'a t -> 'a
val tl : 'a t -> 'a t
val nth : 'a t -> int -> 'a
val nth_opt : 'a t -> int -> 'a option
val rev : 'a t -> 'a t
val init : int -> (int -> 'a) -> 'a t
val append : 'a t -> 'a t -> 'a t
val rev_append : 'a t -> 'a t -> 'a t
val concat : 'a t t -> 'a t
val flatten : 'a t t -> 'a t
val equal : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool
val compare : ('a -> 'a -> int) -> 'a t -> 'a t -> int
val iter : ('a -> unit) -> 'a t -> unit
val iteri : (int -> 'a -> unit) -> 'a t -> unit
val map : ('a -> 'b) -> 'a t -> 'b t
val mapi : (int -> 'a -> 'b) -> 'a t -> 'b t
val rev_map : ('a -> 'b) -> 'a t -> 'b t
val filter_map : ('a -> 'b option) -> 'a t -> 'b t
val concat_map : ('a -> 'b t) -> 'a t -> 'b t
val fold_left_map :
('acc -> 'a -> 'acc * 'b) -> 'acc -> 'a t -> 'acc * 'b t
val fold_left : ('acc -> 'a -> 'acc) -> 'acc -> 'a t -> 'acc
val fold_right : ('a -> 'acc -> 'acc) -> 'a t -> 'acc -> 'acc
val iter2 : ('a -> 'b -> unit) -> 'a t -> 'b t -> unit
val map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
val rev_map2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
val fold_left2 : ('acc -> 'a -> 'b -> 'acc) -> 'acc -> 'a t -> 'b t -> 'acc
val fold_right2 :
('a -> 'b -> 'acc -> 'acc) -> 'a t -> 'b t -> 'acc -> 'acc
val for_all : ('a -> bool) -> 'a t -> bool
val exists : ('a -> bool) -> 'a t -> bool
val for_all2 : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool
val exists2 : ('a -> 'b -> bool) -> 'a t -> 'b t -> bool
val mem : 'a -> 'a t -> bool
val memq : 'a -> 'a t -> bool
val find : ('a -> bool) -> 'a t -> 'a
val find_opt : ('a -> bool) -> 'a t -> 'a option
val find_index : ('a -> bool) -> 'a t -> int option
val find_map : ('a -> 'b option) -> 'a t -> 'b option
val find_mapi : (int -> 'a -> 'b option) -> 'a t -> 'b option
val filter : ('a -> bool) -> 'a t -> 'a t
val find_all : ('a -> bool) -> 'a t -> 'a t
val filteri : (int -> 'a -> bool) -> 'a t -> 'a t
val partition : ('a -> bool) -> 'a t -> 'a t * 'a t
val partition_map : ('a -> ('b, 'c) Either.t) -> 'a t -> 'b t * 'c t
val assoc : 'a -> ('a * 'b) t -> 'b
val assoc_opt : 'a -> ('a * 'b) t -> 'b option
val assq : 'a -> ('a * 'b) t -> 'b
val assq_opt : 'a -> ('a * 'b) t -> 'b option
val mem_assoc : 'a -> ('a * 'b) t -> bool
val mem_assq : 'a -> ('a * 'b) t -> bool
val remove_assoc : 'a -> ('a * 'b) t -> ('a * 'b) t
val remove_assq : 'a -> ('a * 'b) t -> ('a * 'b) t
val split : ('a * 'b) t -> 'a t * 'b t
val combine : 'a t -> 'b t -> ('a * 'b) t
val sort : ('a -> 'a -> int) -> 'a t -> 'a t
val stable_sort : ('a -> 'a -> int) -> 'a t -> 'a t
val fast_sort : ('a -> 'a -> int) -> 'a t -> 'a t
val sort_uniq : ('a -> 'a -> int) -> 'a t -> 'a t
val merge : ('a -> 'a -> int) -> 'a t -> 'a t -> 'a t
val to_seq : 'a t -> 'a Seq.t
val of_seq : 'a Seq.t -> 'a t
val scan_left : ('b -> 'a -> 'b) -> 'b -> 'a t -> 'b t
end
utop # Array.init 10 Fun.id |> Array.scan_left ( + ) 0;;
- : int array = [|0; 1; 3; 6; 10; 15; 21; 28; 36; 45|]
utop # List.init 10 Fun.id |> List.scan_left ( + ) 0;;
- : int list = [0; 1; 3; 6; 10; 15; 21; 28; 36; 45]
module type SeedType = sig
val v : int array
end
module type S = sig
val reset_state : unit -> unit
val bits : unit -> int
val bits32 : unit -> int32
val bits64 : unit -> int64
val nativebits : unit -> nativeint
val int : int -> int
val int32 : int32 -> int32
val int64 : int64 -> int64
val nativeint : nativeint -> nativeint
val full_int : int -> int
val float : float -> float
val bool : unit -> bool
end
module Make(Seed: SeedType) : S = struct
let state = Seed.v |> Random.State.make |> ref
let reset_state () = state := Random.State.make Seed.v
let bits () = Random.State.bits !state
let bits32 () = Random.State.bits32 !state
let bits64 () = Random.State.bits64 !state
let nativebits () = Random.State.nativebits !state
let int = Random.State.int !state
let int32 = Random.State.int32 !state
let int64 = Random.State.int64 !state
let nativeint = Random.State.nativeint !state
let full_int = Random.State.full_int !state
let float = Random.State.float !state
let bool () = Random.State.bool !state
end
utop # #mod_use "random.ml" ;;
module Random :
sig
module type SeedType = sig val v : int array end
module type S =
sig
val reset_state : unit -> unit
val bits : unit -> int
val bits32 : unit -> int32
val bits64 : unit -> int64
val nativebits : unit -> nativeint
val int : int -> int
val int32 : int32 -> int32
val int64 : int64 -> int64
val nativeint : nativeint -> nativeint
val full_int : int -> int
val float : float -> float
val bool : unit -> bool
end
module Make : functor (Seed : SeedType) -> S
end
utop # module R1 = Random.Make(struct let v = [|0; 1; 2; 3|] end);;
module R1 : Random.S
utop # module R2 = Random.Make(struct let v = [|0; 1; 2; 3|] end);;
module R2 : Random.S
utop # R1.bits () ;;
- : int = 75783189
utop # R2.bits () ;;
- : int = 75783189
utop # R1.bits () ;;
- : int = 774473149
utop # R1.reset_state () ;;
- : unit = ()
utop # R2.bits () ;;
- : int = 774473149
utop # R1.bits () ;;
- : int = 75783189
utop # module StringMap = Map.Make(String) ;;
module StringMap :
sig
type key = string
type 'a t = 'a Map.Make(String).t
val empty : 'a t
val add : key -> 'a -> 'a t -> 'a t
val add_to_list : key -> 'a -> 'a list t -> 'a list t
val update : key -> ('a option -> 'a option) -> 'a t -> 'a t
val singleton : key -> 'a -> 'a t
val remove : key -> 'a t -> 'a t
val merge :
(key -> 'a option -> 'b option -> 'c option) -> 'a t -> 'b t -> 'c t
val union : (key -> 'a -> 'a -> 'a option) -> 'a t -> 'a t -> 'a t
val cardinal : 'a t -> int
val bindings : 'a t -> (key * 'a) list
val min_binding : 'a t -> key * 'a
val min_binding_opt : 'a t -> (key * 'a) option
val max_binding : 'a t -> key * 'a
val max_binding_opt : 'a t -> (key * 'a) option
val choose : 'a t -> key * 'a
val choose_opt : 'a t -> (key * 'a) option
val find : key -> 'a t -> 'a
val find_opt : key -> 'a t -> 'a option
val find_first : (key -> bool) -> 'a t -> key * 'a
val find_first_opt : (key -> bool) -> 'a t -> (key * 'a) option
val find_last : (key -> bool) -> 'a t -> key * 'a
val find_last_opt : (key -> bool) -> 'a t -> (key * 'a) option
val iter : (key -> 'a -> unit) -> 'a t -> unit
val fold : (key -> 'a -> 'acc -> 'acc) -> 'a t -> 'acc -> 'acc
val map : ('a -> 'b) -> 'a t -> 'b t
val mapi : (key -> 'a -> 'b) -> 'a t -> 'b t
val filter : (key -> 'a -> bool) -> 'a t -> 'a t
val filter_map : (key -> 'a -> 'b option) -> 'a t -> 'b t
val partition : (key -> 'a -> bool) -> 'a t -> 'a t * 'a t
val split : key -> 'a t -> 'a t * 'a option * 'a t
val is_empty : 'a t -> bool
val mem : key -> 'a t -> bool
val equal : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool
val compare : ('a -> 'a -> int) -> 'a t -> 'a t -> int
val for_all : (key -> 'a -> bool) -> 'a t -> bool
val exists : (key -> 'a -> bool) -> 'a t -> bool
val to_list : 'a t -> (key * 'a) list
val of_list : (key * 'a) list -> 'a t
val to_seq : 'a t -> (key * 'a) Seq.t
val to_rev_seq : 'a t -> (key * 'a) Seq.t
val to_seq_from : key -> 'a t -> (key * 'a) Seq.t
val add_seq : (key * 'a) Seq.t -> 'a t -> 'a t
val of_seq : (key * 'a) Seq.t -> 'a t
end
utop # let int_map : int StringMap.t = StringMap.empty;;
val int_map : int StringMap.t = <abstr>
utop # let float_map : float StringMap.t = StringMap.empty;;
val float_map : float StringMap.t = <abstr>
utop # let float_map : float StringMap.t = StringMap.empty;;
val float_map : float StringMap.t = <abstr>
let lucky_numbers = StringMap.of_seq @@ List.to_seq [
("leostera", 2112);
("charstring88", 88);
("divagnz", 13);
];;
val lucky_numbers : int StringMap.t = <abstr>
utop # let printMap m = StringMap.iter (fun key value -> Printf.printf "%s --> %d\n" key value) m ;;
val printMap : int StringMap.t -> unit = <fun>
utop # printMap lucky_numbers ;;
charstring88 --> 88
divagnz --> 13
leostera --> 2112
- : unit = ()
utop # StringMap.find_opt "leostera" lucky_numbers;;
- : int option = Some 2112
utop # StringMap.find "leostera" lucky_numbers;;
- : int = 2112
utop # let first_under_10_chars : (string * int) option =
StringMap.find_first_opt
(fun key -> String.length key < 10)
lucky_numbers;;
val first_under_10_chars : (string * int) option = Some ("divagnz", 13)
utop # let more_lucky_numbers = lucky_numbers |> StringMap.add "paguzar" 108;;
val more_lucky_numbers : int StringMap.t = <abstr>
utop # StringMap.find_opt "paguzar" lucky_numbers;;
- : int option = None
utop # StringMap.find_opt "paguzar" more_lucky_numbers;;
- : int option = Some 108
utop # let less_lucky_numbers = lucky_numbers |> StringMap.remove "divagnz";;
val less_lucky_numbers : int StringMap.t = <abstr>
utop # StringMap.find_opt "divagnz" lucky_numbers;;
- : int option = Some 13
utop # StringMap.find_opt "divagnz" less_lucky_numbers;;
- : int option = None
utop # let updated_lucky_numbers =
lucky_numbers
|> StringMap.update "charstring88" (Option.map (fun _ -> 99));;
val updated_lucky_numbers : int StringMap.t = <abstr>
utop # StringMap.find_opt "charstring88" lucky_numbers;;
- : int option = Some 88
utop # StringMap.find_opt "charstring88" updated_lucky_numbers;;
- : int option = Some 99
utop # StringMap.mem "paguzar" less_lucky_numbers;;
- : bool = false
utop # StringMap.union;;
- : (string -> 'a -> 'a -> 'a option) ->
'a StringMap.t -> 'a StringMap.t -> 'a StringMap.t
= <fun>
utop # let pick_fst key v1 _ = Some v1;;
val pick_fst : 'a -> 'b -> 'c -> 'b option = <fun>
utop # let pick_snd key _ v2 = Some v2;;
val pick_snd : 'a -> 'b -> 'c -> 'c option = <fun>
utop # let drop _ _ _ = None;;
val drop : 'a -> 'b -> 'c -> 'd option = <fun>
utop # StringMap.(
union pick_fst lucky_numbers updated_lucky_numbers
|> find_opt "charstring88"
);;
- : int option = Some 88
utop # StringMap.(
union pick_snd lucky_numbers updated_lucky_numbers
|> find_opt "charstring88"
);;
- : int option = Some 99
utop # StringMap.(
union drop lucky_numbers updated_lucky_numbers
|> find_opt "charstring88"
);;
- : int option = None
utop # let even_numbers =
StringMap.filter
(fun _ number -> number mod 2 = 0)
lucky_numbers;;
val even_numbers : int StringMap.t = <abstr>
utop # printMap even_numbers ;;
charstring88 --> 88
leostera --> 2112
- : unit = ()
utop # StringMap.map;;
- : ('a -> 'b) -> 'a StringMap.t -> 'b StringMap.t = <fun>
utop # lucky_numbers ;;
- : int StringMap.t = <abstr>
utop # let lucky_strings = StringMap.map string_of_int lucky_numbers;;
val lucky_strings : string StringMap.t = <abstr>
utop # lucky_numbers |> StringMap.find "leostera" |> string_of_int;;
- : string = "2112"
utop # lucky_strings |> StringMap.find "leostera";;
- : string = "2112"
utop # let printStringMap m = StringMap.iter (fun key value -> Printf.printf "%s --> %s\n" key value) m ;;
val printStringMap : string StringMap.t -> unit = <fun>
utop # printStringMap lucky_strings ;;
charstring88 --> 88
divagnz --> 13
leostera --> 2112
- : unit = ()
utop # module Istring : sig
type t
val compare : t -> t -> int
end = struct
type t = string
let compare a b = String.(compare (lowercase_ascii a) (lowercase_ascii b))
end;;
module Istring : sig type t val compare : t -> t -> int end
utop # module IstringMap = Map.Make(Istring);;
module IstringMap :
sig
type key = Istring.t
type 'a t = 'a Map.Make(Istring).t
val empty : 'a t
val add : key -> 'a -> 'a t -> 'a t
val add_to_list : key -> 'a -> 'a list t -> 'a list t
val update : key -> ('a option -> 'a option) -> 'a t -> 'a t
val singleton : key -> 'a -> 'a t
val remove : key -> 'a t -> 'a t
val merge :
(key -> 'a option -> 'b option -> 'c option) -> 'a t -> 'b t -> 'c t
val union : (key -> 'a -> 'a -> 'a option) -> 'a t -> 'a t -> 'a t
val cardinal : 'a t -> int
val bindings : 'a t -> (key * 'a) list
val min_binding : 'a t -> key * 'a
val min_binding_opt : 'a t -> (key * 'a) option
val max_binding : 'a t -> key * 'a
val max_binding_opt : 'a t -> (key * 'a) option
val choose : 'a t -> key * 'a
val choose_opt : 'a t -> (key * 'a) option
val find : key -> 'a t -> 'a
val find_opt : key -> 'a t -> 'a option
val find_first : (key -> bool) -> 'a t -> key * 'a
val find_first_opt : (key -> bool) -> 'a t -> (key * 'a) option
val find_last : (key -> bool) -> 'a t -> key * 'a
val find_last_opt : (key -> bool) -> 'a t -> (key * 'a) option
val iter : (key -> 'a -> unit) -> 'a t -> unit
val fold : (key -> 'a -> 'acc -> 'acc) -> 'a t -> 'acc -> 'acc
val map : ('a -> 'b) -> 'a t -> 'b t
val mapi : (key -> 'a -> 'b) -> 'a t -> 'b t
val filter : (key -> 'a -> bool) -> 'a t -> 'a t
val filter_map : (key -> 'a -> 'b option) -> 'a t -> 'b t
val partition : (key -> 'a -> bool) -> 'a t -> 'a t * 'a t
val split : key -> 'a t -> 'a t * 'a option * 'a t
val is_empty : 'a t -> bool
val mem : key -> 'a t -> bool
val equal : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool
val compare : ('a -> 'a -> int) -> 'a t -> 'a t -> int
val for_all : (key -> 'a -> bool) -> 'a t -> bool
val exists : (key -> 'a -> bool) -> 'a t -> bool
val to_list : 'a t -> (key * 'a) list
val of_list : (key * 'a) list -> 'a t
val to_seq : 'a t -> (key * 'a) Seq.t
val to_rev_seq : 'a t -> (key * 'a) Seq.t
val to_seq_from : key -> 'a t -> (key * 'a) Seq.t
val add_seq : (key * 'a) Seq.t -> 'a t -> 'a t
val of_seq : (key * 'a) Seq.t -> 'a t
end
utop # module StringSet = Set.Make(String) ;;
module StringSet :
sig
type elt = string
type t = Set.Make(String).t
val empty : t
val add : elt -> t -> t
val singleton : elt -> t
val remove : elt -> t -> t
val union : t -> t -> t
val inter : t -> t -> t
val disjoint : t -> t -> bool
val diff : t -> t -> t
val cardinal : t -> int
val elements : t -> elt list
val min_elt : t -> elt
val min_elt_opt : t -> elt option
val max_elt : t -> elt
val max_elt_opt : t -> elt option
val choose : t -> elt
val choose_opt : t -> elt option
val find : elt -> t -> elt
val find_opt : elt -> t -> elt option
val find_first : (elt -> bool) -> t -> elt
val find_first_opt : (elt -> bool) -> t -> elt option
val find_last : (elt -> bool) -> t -> elt
val find_last_opt : (elt -> bool) -> t -> elt option
val iter : (elt -> unit) -> t -> unit
val fold : (elt -> 'acc -> 'acc) -> t -> 'acc -> 'acc
val map : (elt -> elt) -> t -> t
val filter : (elt -> bool) -> t -> t
val filter_map : (elt -> elt option) -> t -> t
val partition : (elt -> bool) -> t -> t * t
val split : elt -> t -> t * bool * t
val is_empty : t -> bool
val mem : elt -> t -> bool
val equal : t -> t -> bool
val compare : t -> t -> int
val subset : t -> t -> bool
val for_all : (elt -> bool) -> t -> bool
val exists : (elt -> bool) -> t -> bool
val to_list : t -> elt list
val of_list : elt list -> t
val to_seq_from : elt -> t -> elt Seq.t
val to_seq : t -> elt Seq.t
val to_rev_seq : t -> elt Seq.t
val add_seq : elt Seq.t -> t -> t
val of_seq : elt Seq.t -> t
end
utop # StringSet.empty ;;
- : StringSet.t = <abstr>
utop # StringSet.empty |> StringSet.to_list;;
- : string list = []
utop # StringSet.singleton "hello";;
- : StringSet.t = <abstr>
utop # StringSet.(singleton "hello" |> to_list);;
- : string list = ["hello"]
utop # StringSet.of_list ["hello"; "hi"];;
- : StringSet.t = <abstr>
utop # StringSet.(of_list ["hello"; "hi"] |> to_list);;
- : string list = ["hello"; "hi"]
utop # let first_set = ["hello"; "hi"] |> StringSet.of_list;;
val first_set : StringSet.t = <abstr>
utop # let second_set = ["good morning"; "hi"] |> StringSet.of_list;;
val second_set : StringSet.t = <abstr>
utop # StringSet.(first_set |> add "good morning" |> to_list);;
- : string list = ["good morning"; "hello"; "hi"]
utop # StringSet.(first_set |> remove "hello" |> to_list);;
- : string list = ["hi"]
utop # StringSet.(union first_set second_set |> to_list);;
- : string list = ["good morning"; "hello"; "hi"]
utop # StringSet.(inter first_set second_set |> to_list);;
- : string list = ["hi"]
utop # StringSet.(diff first_set second_set |> to_list);;
- : string list = ["hello"]
utop # ["good morning"; "hello"; "hi"]
|> StringSet.of_list
|> StringSet.filter (fun str -> String.length str <= 5)
|> StringSet.to_list;;
- : string list = ["hello"; "hi"]
utop # ["good morning"; "hello"; "hi"]
|> StringSet.of_list
|> StringSet.mem "hello";;
- : bool = true
module CISS = Set.Make(struct
type t = string
let compare a b = compare (String.lowercase_ascii a) (String.lowercase_ascii b)
end);;
module CISS :
sig
type elt = string
type t
val empty : t
val add : elt -> t -> t
val singleton : elt -> t
val remove : elt -> t -> t
val union : t -> t -> t
val inter : t -> t -> t
val disjoint : t -> t -> bool
val diff : t -> t -> t
val cardinal : t -> int
val elements : t -> elt list
val min_elt : t -> elt
val min_elt_opt : t -> elt option
val max_elt : t -> elt
val max_elt_opt : t -> elt option
val choose : t -> elt
val choose_opt : t -> elt option
val find : elt -> t -> elt
val find_opt : elt -> t -> elt option
val find_first : (elt -> bool) -> t -> elt
val find_first_opt : (elt -> bool) -> t -> elt option
val find_last : (elt -> bool) -> t -> elt
val find_last_opt : (elt -> bool) -> t -> elt option
val iter : (elt -> unit) -> t -> unit
val fold : (elt -> 'acc -> 'acc) -> t -> 'acc -> 'acc
val map : (elt -> elt) -> t -> t
val filter : (elt -> bool) -> t -> t
val filter_map : (elt -> elt option) -> t -> t
val partition : (elt -> bool) -> t -> t * t
val split : elt -> t -> t * bool * t
val is_empty : t -> bool
val mem : elt -> t -> bool
val equal : t -> t -> bool
val compare : t -> t -> int
val subset : t -> t -> bool
val for_all : (elt -> bool) -> t -> bool
val exists : (elt -> bool) -> t -> bool
val to_list : t -> elt list
val of_list : elt list -> t
val to_seq_from : elt -> t -> elt Seq.t
val to_seq : t -> elt Seq.t
val to_rev_seq : t -> elt Seq.t
val add_seq : elt Seq.t -> t -> t
val of_seq : elt Seq.t -> t
end
utop # CISS.singleton "hello" |> CISS.add "HELLO" |> CISS.to_list;;
- : string list = ["hello"]
utop # type color = Red | Green | Blue;;
type color = Red | Green | Blue
utop # module SC = Set.Make(struct
type t = color
let compare a b =
match a, b with
| (Red, Red) -> 0
| (Red, Green) -> 1
| (Red, Blue) -> 1
| (Green, Red) -> -1
| (Green, Green) -> 0
| (Green, Blue) -> 1
| (Blue, Red) -> -1
| (Blue, Green) -> -1
| (Blue, Blue) -> 0
end);;
module SC :
sig
type elt = color
type t
val empty : t
val add : elt -> t -> t
val singleton : elt -> t
val remove : elt -> t -> t
val union : t -> t -> t
val inter : t -> t -> t
val disjoint : t -> t -> bool
val diff : t -> t -> t
val cardinal : t -> int
val elements : t -> elt list
val min_elt : t -> elt
val min_elt_opt : t -> elt option
val max_elt : t -> elt
val max_elt_opt : t -> elt option
val choose : t -> elt
val choose_opt : t -> elt option
val find : elt -> t -> elt
val find_opt : elt -> t -> elt option
val find_first : (elt -> bool) -> t -> elt
val find_first_opt : (elt -> bool) -> t -> elt option
val find_last : (elt -> bool) -> t -> elt
val find_last_opt : (elt -> bool) -> t -> elt option
val iter : (elt -> unit) -> t -> unit
val fold : (elt -> 'acc -> 'acc) -> t -> 'acc -> 'acc
val map : (elt -> elt) -> t -> t
val filter : (elt -> bool) -> t -> t
val filter_map : (elt -> elt option) -> t -> t
val partition : (elt -> bool) -> t -> t * t
val split : elt -> t -> t * bool * t
val is_empty : t -> bool
val mem : elt -> t -> bool
val equal : t -> t -> bool
val compare : t -> t -> int
val subset : t -> t -> bool
val for_all : (elt -> bool) -> t -> bool
val exists : (elt -> bool) -> t -> bool
val to_list : t -> elt list
val of_list : elt list -> t
val to_seq_from : elt -> t -> elt Seq.t
val to_seq : t -> elt Seq.t
val to_rev_seq : t -> elt Seq.t
val add_seq : elt Seq.t -> t -> t
val of_seq : elt Seq.t -> t
end
Source: https://ocaml.org/docs/hash-tables
utop # let my_hash = Hashtbl.create 123456;;
val my_hash : ('_weak1, '_weak2) Hashtbl.t = <abstr>
utop # my_hash ;;
- : ('_weak1, '_weak2) Hashtbl.t = <abstr>
utop # Hashtbl.add my_hash "h" "hello";
Hashtbl.add my_hash "h" "hi";
Hashtbl.add my_hash "h" "hug";
Hashtbl.add my_hash "h" "hard";
Hashtbl.add my_hash "w" "wimp";
Hashtbl.add my_hash "w" "world";
Hashtbl.add my_hash "w" "wine";;
- : unit = ()
utop # Hashtbl.find my_hash "h";;
- : string = "hard"
utop # Hashtbl.find_all my_hash "h";;
- : string list = ["hard"; "hug"; "hi"; "hello"]
utop # Hashtbl.remove my_hash "h";;
- : unit = ()
utop # Hashtbl.find my_hash "h";;
- : string = "hug"
utop # Hashtbl.find_all my_hash "h";;
- : string list = ["hug"; "hi"; "hello"]
utop # Hashtbl.replace my_hash "t" "try";
Hashtbl.replace my_hash "t" "test";
Hashtbl.find_all my_hash "t";;
- : string list = ["test"]
utop # Hashtbl.remove my_hash "t";
Hashtbl.find my_hash "t";;
Exception: Not_found.
utop # Hashtbl.mem my_hash "h";;
- : bool = true