Reading — step 1 of 7
Learn
OCaml is functional FIRST but supports imperative style when needed. Mutable references, arrays, and for/while loops are all available — use sparingly, where they fit.
References — single-cell mutation
let counter = ref 0
incr counter (* counter := !counter + 1 *)
incr counter
incr counter
Printf.printf "%d\n" !counter (* 3 — dereference with ! *)
ref initial_value— create a mutable cell!r— dereference (read the contents)r := new_value— assignincr r/decr r— increment / decrement int refs
Refs hold a single value of any type. Useful for counters, accumulators in imperative loops, or implementing stateful APIs.
Arrays — fixed-size mutable
let arr = [| 1; 2; 3; 4; 5 |] (* | brackets *)
arr.(0) (* 1 — read *)
arr.(2) <- 99 (* set *)
Array.length arr (* 5 *)
Array.iter print_int arr (* 1299145 *)
Array.map (fun x -> x * 2) arr (* [|2; 4; 198; 8; 10|] *)
[| ... |]— array literalarr.(i)— index access (parens around index, NOT brackets)arr.(i) <- v— assignment
Arrays are MUTABLE and FIXED-SIZE. Different from lists (immutable, linked).
When to use arrays vs lists
- List — immutable, easy to prepend, pattern-match, recurse over
- Array — fixed size known up front, fast random access, in-place updates
Most OCaml code uses lists. Arrays for performance-critical numeric or random-access work.
for and while loops
for i = 0 to 4 do
Printf.printf "%d " i
done
(* 0 1 2 3 4 *)
for i = 4 downto 0 do
Printf.printf "%d " i
done
(* 4 3 2 1 0 *)
let i = ref 0 in
while !i < 5 do
Printf.printf "%d " !i;
incr i
done
for loops are inclusive: for i = 1 to 10 iterates 1..=10. to for ascending; downto for descending. while for general loops with mutable conditions.
The loop body must have type unit. Use ; to sequence multiple statements.
Mutable record fields
type counter = { mutable count : int }
let c = { count = 0 }
c.count <- c.count + 1
c.count <- c.count + 1
Printf.printf "%d\n" c.count (* 2 *)
mutable field allows in-place modification with <-. Other fields stay immutable.
Hashtbl — mutable hash maps
let tbl = Hashtbl.create 16
Hashtbl.add tbl "alice" 30
Hashtbl.add tbl "bob" 25
Hashtbl.find tbl "alice" (* 30 *)
Hashtbl.find_opt tbl "missing" (* None *)
Hashtbl.mem tbl "alice" (* true *)
Hashtbl.remove tbl "bob"
Hashtbl.iter (fun k v -> Printf.printf "%s:%d " k v) tbl
Mutable in-place hash table. Useful for caches, frequency counts, set-like usage.
For immutable maps, use the Map module (functor-based, slightly more ceremonial).
Sequence operators — ; and let-binding
let () =
print_string "hello ";
print_string "world";
print_newline ()
; separates statements (each must have type unit). For binding values, use let:
let name = read_line () in
let age = int_of_string (read_line ()) in
Printf.printf "%s is %d\n" name age
When to mix functional and imperative
Yes:
- Performance-critical inner loops (mutable accumulator)
- Hash tables for fast lookup
- Implementing stateful APIs (caches, generators)
No:
- Default coding style
- Public API surface (return values, not mutate parameters)
- When pattern matching + recursion is clearer
Functional code is the OCaml default; reach for imperative where it pays.
Common mistakes
- Forgetting
!to read a ref — usingrdirectly gives the ref, not the contents. - Using
[]for array access — OCaml usesarr.(i), notarr.[i](brackets are for STRINGS). - Forgetting
;between statements — sequencing operator. - Mixing mutable refs with functional flow — don't pass ref-holding records around as if immutable.
- Hashtbl.add when you meant replace —
addkeeps duplicate keys (with stack semantics). UseHashtbl.replaceto overwrite.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…