Skip to content

Latest commit

 

History

History
5582 lines (4017 loc) · 122 KB

ocaml.org

File metadata and controls

5582 lines (4017 loc) · 122 KB

1. Tour of OCaml

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 );;

2. Values and Functions

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 ;;

3. Basic Data Types and Pattern Matching

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:

  1. Variant
  2. Record
  3. 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"))) ;;

4. Lists

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] ;;

Functions on Lists

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;;

Higher Order Functions on Lists

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]];;

The Standard Library List Module

Maps and Iterators

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];;

List Scanning

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.

List Searching

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];;

Association Lists

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"];;

Sorting List

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)];;

Folds

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
      ([], []);;

Lists and Tail Recursion

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;;

5. Loops and Recursions

Reference: https://ocaml.org/docs/loops-recursion

For Loops and While Loops

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 ;;

Looping Over Lists

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 ;;

Looping Over Strings

String module has String.copy, String.iter functions and more.

Recursion

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:

Mutable Records, References (Again!) and Arrays

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.

Mutually Recursive Functions

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.

6. Opam Install

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

7. Introduction to Opam Switches

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.

8. Your First OCaml Program

Compiling OCaml Programs

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

Watch Mode

$ opam exec -- dune build -w

OR

$ opam exec -- dune exec hello -w

Why Isn’t There a Main Function?

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 () =”.

Modules and the Standard Library

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!"

Every File Defines a Module

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

Defining Module Interfaces

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.

Defining Multiple Modules in a Library

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

Installing and Using Modules from a Package

$ 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

Using the Preprocessor to Generate Code

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

Minimum setup

$ 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.

9. utop and IO

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);;

10. Labelled and Optional Arguments

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.

Passing Labelled Arguments

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

Labelling Parameters

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.

Passing Optional Arguments

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

Defining Optional Parameters with Default Values

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

Defining Optional Parameters Without Default Values

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"

Optional Arguments and Partial Application

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.

Passing Labelled Arguments Using the Pipe Operator

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]

Function with Only Optional Arguments

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.

Forwarding an Optional Argument

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.

11. Mutability and Imperative Control Flow

Immutable vs Mutable Data

The let name-value inding is immutable.

References

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>

Mutable Record Fields

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.

Arrays

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|]

Byte Sequences

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.

Example: get_char Function

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:

  1. Read and record the terminal attributes
  2. Set the terminal attributes
  3. Wait until a key is pressed, read it as a character
  4. Restore the initial terminal attributes
  5. 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'

Imperative Control Flow

Evaluting Expressios in Sequence

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:

  1. Increment r
  2. Compute 2 * !r
  3. 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 = ()

if … then … else … and Side Effects

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

For Loop

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

While Loop

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 = ()

Breaking Loops Using Exceptions

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 = ()

References Inside Closures

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

Recommendations for Mutable State and Side Effects

We show some patterns and anti-patterns relating to mutable states and side effects:

Good: Function-Encapsulated Mutability

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.

Good: Application-Wide State

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”.

Good: Precomputing Values

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

Good: Memoization

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.

Good: Functional by Default

OCaml programs should be written in a mostly functional style.

Avoid side effects where possible and relying on immutable data instead of mutable state.

It Depends: Module 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.

Bad: Undocumented Mutation

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.

Bad: Undocumented Side Effects

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.

Bad: Side Effects Depending on Order of Evaluation

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.

12. Options

Reference: https://ocaml.org/docs/options

Introduction

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.

Exceptions vs Options

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

The Standard Library Option Module

Most of the functions are provided by Stdlib.Option module.

Map Over an Option

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

Peel-Off Doubly Wrapped Options

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

Access the Content of an Option

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

Fold an Option

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"]

Bind an Option

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>

13. Arrays

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:

  1. Storing and processing large amounts of data.
  2. Implementing algorithms that require random access and modification of elements.
  3. Working with matrices and other multi-dimensional data structures.

Creating Arrays

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|]

Accessing Array Elements

You can access individual elements of an array using the .(index) syntax.

utop #  even_numbers.(2);;
- : int = 4

Modifying Array Elements

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.

The Standard Library Array Module

Length of an Array

Array.length function returns the size of an array:

utop # Array.length even_numbers;;
- : int = 5

Iterate on an Array

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 = ()

Map an Array

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|]

Folding an Array

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

Sorting an Array

To sort an array, we can use the Array.sort function. This function takes as arguments:

  1. A comparison function.
  2. 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|]

Copying Part of an Aray into Another Array

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.

14. Sequences

Reference:

Introduction

Sequences are very much like lists.

They may be infinite.

Examples:

  1. A stream of incoming requests in a server,
  2. readings from an embedded sensor,
  3. 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:

  1. Empty lists and sequences are defined the same way, a constructor without any parameters: Seq.Nil and [].
  2. Non-empty lists and sequences are both pairs whose former member is a piece of data.
  3. 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?).

Example

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:

  1. a sequence called seq
  2. 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.

Unfolding Sequences

Standard higher-order iteration functions are available on sequences:

  1. Seq.iter
  2. Seq.map
  3. 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 = ()

Sequences are Functions

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

Sequences for Conversions

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>

15. Modules

Basic Usage

File-Based Modules

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

Naming and Scoping

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>

Interfaces and Implementations

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

Abstract and Read-Only Types

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>

Submodules

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"

Module Manipulation

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

Stateful Modules

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

16. Functors

Write a Functor to Extend Modules

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]

Initialisation of Stateful Modules

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

17. Maps

Introduction

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

Creating a Map

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>

Working with Maps

 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 = ()

Finding Entries in a Map

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)

Adding Entries to a Map

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

Removing Entries from a Map

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

Changing the value associated with a key

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

Checking if a key is contained in a map

utop # StringMap.mem "paguzar" less_lucky_numbers;;
- : bool = false

Merging Maps

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

Filtering a Map

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 = ()

Map a Map

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 = ()

Maps with Custom Key Types

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

18. Sets and Hash Tables

Introduction

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

Creating a Set

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"]

Working with Sets

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

Sets with Custom Comparators

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

Hash Tables

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