Two utility functions:
prefix_sums xs builds Array of len n+1 such that
arr.(i) = sum of xs[0..i-1]
range_sum p lo hi = p.(hi+1) - p.(lo)
For [3;1;4;1;5;9;2;6;5;3]:
range_sum 0 4 = 14 (3+1+4+1+5)
range_sum 5 9 = 25 (9+2+6+5+3)
range_sum 2 7 = 27 (4+1+5+9+2+6)
sum = 66
Tests List.iter mutating Array indexed by a ref counter, plus the
classic prefix-sum technique for O(1) range queries.
67 baseline programs total.
Board as 9-element flat int array, 0=empty, 1=X, 2=O. Three
predicate functions:
check_row b r check_col b c check_diag b
each return the winning player's mark or 0. Main 'winner' loops
i = 0..2 calling row(i)/col(i) then check_diag, threading via a
result ref.
Test board:
X X X
. O .
. . O
X wins on row 0 -> winner returns 1.
Tests Array.of_list with row-major 'b.(r * 3 + c)' indexing,
multi-fn collaboration, and structural equality on int values.
66 baseline programs total.
Pure recursion — at each element, take it or don't:
let rec count_subsets xs target =
match xs with
| [] -> if target = 0 then 1 else 0
| x :: rest ->
count_subsets rest target
+ count_subsets rest (target - x)
For [1;2;3;4;5;6;7;8] target 10, the recursion tree has 2^8 = 256
leaves. Returns 8 — number of subsets summing to 10:
1+2+3+4, 1+2+7, 1+3+6, 1+4+5, 2+3+5, 2+8, 3+7, 4+6 = 8
Tests doubly-recursive list traversal pattern with single-arg list
+ accumulator-via-target.
65 baseline programs total.
Iterative Collatz / hailstone sequence:
let collatz_length n =
let m = ref n in
let count = ref 0 in
while !m > 1 do
if !m mod 2 = 0 then m := !m / 2
else m := 3 * !m + 1;
count := !count + 1
done;
!count
27 is the famous 'long-running' Collatz starter. Reaches a peak of
9232 mid-sequence and takes 111 steps to bottom out at 1.
64 baseline programs total.
Walks list with List.iteri, checking if target - x is already in
the hashtable; if yes, the earlier index plus current is the
answer; otherwise record the current pair.
twosum [2;7;11;15] 9 = (0, 1) 2+7
twosum [3;2;4] 6 = (1, 2) 2+4
twosum [3;3] 6 = (0, 1) 3+3
Sum of i+j over each pair: 1 + 3 + 1 = 5.
Tests Hashtbl.find_opt + add (the iter-99 cleanup), List.iteri, and
tuple destructuring on let-binding (iter 98 'let (i, j) = twosum
... in').
63 baseline programs total.
Bisection method searching for f(x) = 0 in [lo, hi] over 50
iterations:
let bisect f lo hi =
let lo = ref lo and hi = ref hi in
for _ = 1 to 50 do
let mid = (!lo +. !hi) /. 2.0 in
if f mid = 0.0 || f !lo *. f mid < 0.0 then hi := mid
else lo := mid
done;
!lo
Solving x^2 - 2 = 0 in [1, 2] via 'bisect (fun x -> x *. x -. 2.0)
1.0 2.0' converges to ~1.41421356... -> int_of_float (r *. 100) =
141.
Tests:
- higher-order function passing
- multi-let 'let lo = ref ... and hi = ref ...'
- float arithmetic
- int_of_float truncate-toward-zero (iter 117)
62 baseline programs total.
36-character digit alphabet '0..9A..Z' supports any base 2..36. Loop
divides the magnitude by base and prepends the digit:
while !m > 0 do
acc := String.make 1 digits.[!m mod base] ^ !acc;
m := !m / base
done
Special-cases n = 0 -> '0' and prepends '-' for negatives.
Test cases (length, since the strings differ in alphabet):
255 hex 'FF' 2
1024 binary '10000000000' 11
100 dec '100' 3
0 any base '0' 1
sum 17
Combines digits.[i] (string indexing) + String.make 1 ch + String
concatenation in a loop.
61 baseline programs total.
Three refs threading through a while loop:
m remaining quotient
d current divisor
result accumulator (built in reverse, List.rev at end)
while !m > 1 do
if !m mod !d = 0 then begin
result := !d :: !result;
m := !m / !d
end else
d := !d + 1
done
360 = 2^3 * 3^2 * 5 factors to [2;2;2;3;3;5], sum 17.
60 baseline programs total — milestone.
Models a bank account using a mutable record + a user exception:
type account = { mutable balance : int }
exception Insufficient
let withdraw acct amt =
if amt > acct.balance then raise Insufficient
else acct.balance <- acct.balance - amt
Sequence:
start 100
deposit 50 150
withdraw 30 120
withdraw 200 raises Insufficient
handler returns acct.balance (= 120, transaction rolled back)
Combines mutable record fields, user exception declaration,
try-with-bare-pattern, and verifies that a raise in the middle of a
sequence doesn't leave a partial mutation.
59 baseline programs total.
to_counts builds a 256-slot int array of character frequencies:
let to_counts s =
let counts = Array.make 256 0 in
for i = 0 to String.length s - 1 do
let c = Char.code s.[i] in
counts.(c) <- counts.(c) + 1
done;
counts
same_counts compares two arrays element-by-element via for loop +
bool ref. is_anagram composes them.
Four pairs:
listen ~ silent true
hello !~ world false
anagram ~ nagaram true
abc !~ abcd false (length differs)
sum 2
Exercises Array.make + arr.(i) + arr.(i) <- v + nested for loops +
Char.code + s.[i].
57 baseline programs total.
Defines a user exception with int payload:
exception Negative of int
let safe_sqrt n =
if n < 0 then raise (Negative n)
else <integer sqrt via while loop>
let try_sqrt n =
try safe_sqrt n with
| Negative x -> -x
try_sqrt 16 -> 4
try_sqrt 25 -> 5
try_sqrt -7 -> 7 (handler returns -(-7) = 7)
try_sqrt 100 -> 10
sum -> 26
Tests exception declaration with int payload, raise with carry, and
try-with arm pattern-matching the constructor with payload binding.
56 baseline programs total.
Defines a parametric tree:
type 'a tree = Leaf of 'a | Node of 'a tree list
let rec flatten t =
match t with
| Leaf x -> [x]
| Node ts -> List.concat (List.map flatten ts)
Test tree has 3 levels of nesting:
Node [Leaf 1; Node [Leaf 2; Leaf 3];
Node [Node [Leaf 4]; Leaf 5; Leaf 6];
Leaf 7]
flattens to [1;2;3;4;5;6;7] -> sum = 28.
Tests parametric ADT, mutual recursion via map+self, List.concat.
55 baseline programs total.
Two-line baseline:
let rec gcd a b = if b = 0 then a else gcd b (a mod b)
let lcm a b = a * b / gcd a b
gcd 36 48 = 12
lcm 4 6 = 12
lcm 12 18 = 36
sum = 60
Tests mod arithmetic and the integer-division fix from iteration 94
(without truncate-toward-zero, 'lcm 4 6 = 4 * 6 / 2 = 12.0' rather
than the expected 12).
54 baseline programs total.
zip walks both lists in lockstep, truncating at the shorter. unzip
uses tuple-pattern destructuring on the recursive result.
let pairs = zip [1;2;3;4] [10;20;30;40] in
let (xs, ys) = unzip pairs in
List.fold_left (+) 0 xs * List.fold_left (+) 0 ys
= 10 * 100
= 1000
Exercises:
- tuple-cons patterns in match scrutinee: 'match (xs, ys) with'
- tuple constructor in return value: '(a :: la, b :: lb)'
- the iter-98 let-tuple destructuring: 'let (la, lb) = unzip rest'
53 baseline programs total.
Recursive 4-arm match on (a, b) tuples threading a carry:
match (a, b) with
| ([], []) -> if carry = 0 then [] else [carry]
| (x :: xs, []) -> (s mod 10) :: aux xs [] (s / 10) where s = x + carry
| ([], y :: ys) -> ...
| (x :: xs, y :: ys) -> ... where s = x + y + carry
Little-endian digit lists. Three tests:
[9;9;9] + [1] = [0;0;0;1] (=1000, digit sum 1)
[5;6;7] + [8;9;1] = [3;6;9] (=963, digit sum 18)
[9;9;9;9;9;9;9;9] + [1] length 9 (carry propagates 8x)
Sum = 1 + 18 + 9 = 28.
Exercises tuple-pattern match on nested list-cons with the integer
arithmetic and carry-threading idiom typical of multi-precision
implementations.
52 baseline programs total.
Recursive ADT with three constructors (Num/Add/Mul). simp does
bottom-up rewrite using algebraic identities:
x + 0 -> x
0 + x -> x
x * 0 -> 0
0 * x -> 0
x * 1 -> x
1 * x -> x
constant folding for Num + Num and Num * Num
Uses tuple pattern in nested match: 'match (simp a, simp b) with'.
Add (Mul (Num 3, Num 5), Add (Num 0, Mul (Num 1, Num 7)))
-> simp -> Add (Num 15, Num 7)
-> eval -> 22
51 baseline programs total.
Triple-nested for loop with row-major indexing:
for i = 0 to n - 1 do
for j = 0 to n - 1 do
for k = 0 to n - 1 do
c.(i * n + j) <- c.(i * n + j) + a.(i * n + k) * b.(k * n + j)
done
done
done
For 3x3 matrices A=[[1..9]] and B=[[9..1]], the resulting C has sum
621. Tests deeply nested for loops on Array, Array.make + arr.(i) +
arr.(i) <- v + Array.fold_left.
50 baseline programs total — milestone.
Iterative binary search on a sorted int array:
let bsearch arr target =
let n = Array.length arr in
let lo = ref 0 and hi = ref (n - 1) in
let found = ref (-1) in
while !lo <= !hi && !found = -1 do
let mid = (!lo + !hi) / 2 in
if arr.(mid) = target then found := mid
else if arr.(mid) < target then lo := mid + 1
else hi := mid - 1
done;
!found
For [1;3;5;7;9;11;13;15;17;19;21]:
bsearch a 13 = 6
bsearch a 5 = 2
bsearch a 100 = -1
sum = 7
Exercises Array.of_list + arr.(i) + multi-let 'let lo = ... and
hi = ...' + while + multi-arm if/else if/else.
49 baseline programs total.
Two-pointer palindrome check:
let is_palindrome s =
let n = String.length s in
let rec check i j =
if i >= j then true
else if s.[i] <> s.[j] then false
else check (i + 1) (j - 1)
in
check 0 (n - 1)
Tests on six strings:
racecar = true
hello = false
abba = true
'' = true (vacuously, i >= j on entry)
'a' = true
'ab' = false
Sum = 4.
Uses s.[i] <> s.[j] (string-get + structural inequality), recursive
2-arg pointer advancement, and a multi-clause if/else if/else for
the three cases.
48 baseline programs total.
Bottom-up dynamic programming. dp[i] = minimum coins to make
amount i.
let dp = Array.make (target + 1) (target + 1) in (* sentinel *)
dp.(0) <- 0;
for i = 1 to target do
List.iter (fun c ->
if c <= i && dp.(i - c) + 1 < dp.(i) then
dp.(i) <- dp.(i - c) + 1
) coins
done
Sentinel 'target + 1' means impossible — any real solution uses at
most 'target' coins.
coin_change [1; 5; 10; 25] 67 = 6 (= 25+25+10+5+1+1)
Exercises Array.make + arr.(i) + arr.(i) <- v + nested
for/List.iter + guard 'c <= i'.
47 baseline programs total.
Kadane's algorithm in O(n):
let max_subarray xs =
let max_so_far = ref min_int in
let cur = ref 0 in
List.iter (fun x ->
cur := max x (!cur + x);
max_so_far := max !max_so_far !cur
) xs;
!max_so_far
For [-2;1;-3;4;-1;2;1;-5;4] the optimal subarray is [4;-1;2;1] = 6.
Exercises min_int (iter 94), max as global, ref / ! / :=, and
List.iter with two side-effecting steps in one closure body.
46 baseline programs total.
next_row prepends 1, walks adjacent pairs (x, y) emitting x+y,
appends a final 1:
let rec next_row prev =
let rec aux a =
match a with
| [_] -> [1]
| x :: y :: rest -> (x + y) :: aux (y :: rest)
| [] -> []
in
1 :: aux prev
row n iterates next_row n times starting from [1] using a ref +
'for _ = 1 to n do r := next_row !r done'.
row 10 = [1;10;45;120;210;252;210;120;45;10;1]
List.nth (row 10) 5 = 252 = C(10, 5)
Exercises three-arm match including [_] singleton wildcard, x :: y
:: rest binding, and the for-loop with wildcard counter. 45 baseline
programs total.
Run-length encoding via tail-recursive 4-arg accumulator:
let rle xs =
let rec aux xs cur n acc =
match xs with
| [] -> List.rev ((cur, n) :: acc)
| h :: t ->
if h = cur then aux t cur (n + 1) acc
else aux t h 1 ((cur, n) :: acc)
in
match xs with
| [] -> []
| h :: t -> aux t h 1 []
rle [1;1;1;2;2;3;3;3;3;1;1] = [(1,3);(2,2);(3,4);(1,2)]
sum of counts = 11 (matches input length)
The sum-of-counts test verifies that the encoding preserves total
length — drops or duplicates would diverge.
44 baseline programs total.
Defines a recursive str_contains that walks the haystack with
String.sub to find a needle substring. Real OCaml's String.contains
only accepts a single char, so this baseline implements its own
substring search to stay portable.
let rec str_contains s sub i =
if i + sl > nl then false
else if String.sub s i sl = sub then true
else str_contains s sub (i + 1)
count_matching splits text on newlines, folds with the predicate.
'the quick brown fox\nfox runs fast\nthe dog\nfoxes are clever'
needle = 'fox'
matches = 3 (lines 1, 2, 4 — 'foxes' contains 'fox')
43 baseline programs total.
The binop precedence table already had land/lor/lxor/lsl/lsr/asr
(iter 0 setup) but eval-op fell through to 'unknown operator' for
all of them. SX doesn't expose host bitwise primitives, so each is
implemented in eval.sx via arithmetic on the host:
land/lor/lxor: mask & shift loop, accumulating 1<<k digits
lsl k: repeated * 2 k times
lsr k: repeated floor (/ 2) k times
asr: aliased to lsr (no sign extension at our bit width)
bits.ml baseline: popcount via 'while m > 0 do if m land 1 = 1 then
... ; m := m lsr 1 done'. Sum of popcount(1023, 5, 1024, 0xff) = 10
+ 2 + 1 + 8 = 21.
5 land 3 = 1
5 lor 3 = 7
5 lxor 3 = 6
1 lsl 8 = 256
256 lsr 4 = 16
41 baseline programs total.
Classic Ackermann function:
let rec ack m n =
if m = 0 then n + 1
else if n = 0 then ack (m - 1) 1
else ack (m - 1) (ack m (n - 1))
ack(3, 4) = 125, expanding to ~6700 evaluator frames — a useful
stress test of the call stack and control transfer. Real OCaml
evaluates this in milliseconds; ours takes ~2 minutes on a
contended host but completes correctly.
40 baseline programs total.
Stack-based RPN evaluator:
let eval_rpn tokens =
let stack = Stack.create () in
List.iter (fun tok ->
if tok is operator then
let b = Stack.pop stack in
let a = Stack.pop stack in
Stack.push (apply tok a b) stack
else
Stack.push (int_of_string tok) stack
) tokens;
Stack.pop stack
For tokens [3 4 + 2 * 5 -]:
3 4 + -> 7
7 2 * -> 14
14 5 - -> 9
Exercises Stack.create / push / pop, mixed branch on string
equality, multi-arm if/else if for operator dispatch, int_of_string
for token parsing.
39 baseline programs total.
Newton's method for square root:
let sqrt_newton x =
let g = ref 1.0 in
for _ = 1 to 20 do
g := (!g +. x /. !g) /. 2.0
done;
!g
20 iterations is more than enough to converge for x=2 — result is
~1.414213562. Multiplied by 1000 and int_of_float'd: 1414.
First baseline exercising:
- for _ = 1 to N do ... done (wildcard loop variable)
- pure float arithmetic with +. /.
- the int_of_float truncate-toward-zero fix from iter 117
38 baseline programs total.
Classic doubly-recursive solution returning the move count:
hanoi n from to via =
if n = 0 then 0
else hanoi (n-1) from via to + 1 + hanoi (n-1) via to from
For n = 10, returns 2^10 - 1 = 1023.
Exercises 4-arg recursion, conditional base case, and tail-position
addition. Uses 'to_' instead of 'to' for the destination param to
avoid collision with the 'to' keyword in for-loops — the OCaml
conventional workaround.
37 baseline programs total.
validate_int returns Left msg on empty / non-digit, Right
(int_of_string s) on a digit-only string. process folds inputs with a
tuple accumulator (errs, sum), branching on the result.
['12'; 'abc'; '5'; ''; '100'; 'x']
-> 3 errors (abc, '', x), valid sum = 12+5+100 = 117
-> errs * 100 + sum = 417
Exercises:
- Either constructors used bare (Left/Right without 'Either.'
qualification)
- char range comparison: c >= '0' && c <= '9'
- tuple-pattern destructuring on let-binding (iter 98)
- recursive helper defined inside if-else
- List.fold_left with tuple accumulator
36 baseline programs total.
First baseline using Map.Make on a string-keyed map:
module StringOrd = struct
type t = string
let compare = String.compare
end
module SMap = Map.Make (StringOrd)
let count_words text =
let words = String.split_on_char ' ' text in
List.fold_left (fun m w ->
let n = match SMap.find_opt w m with
| Some n -> n
| None -> 0
in
SMap.add w (n + 1) m
) SMap.empty words
For 'the quick brown fox jumps over the lazy dog' ('the' appears
twice), SMap.cardinal -> 8.
Complements bag.ml (Hashtbl-based) and unique_set.ml (Set.Make)
with a sorted Map view of the same kind of counting problem. 35
baseline programs total.
Either module (mirrors OCaml 4.12+ stdlib):
left x / right x
is_left / is_right
find_left / find_right (return Option)
map_left / map_right (single-side mappers)
fold lf rf e (case dispatch)
equal eq_l eq_r a b
compare cmp_l cmp_r a b (Left < Right)
Constructors are bare 'Left x' / 'Right x' (OCaml 4.12+ exposes them
directly without an explicit type-decl).
Hashtbl.copy:
build a fresh cell with _hashtbl_create
walk _hashtbl_to_list and re-add each (k, v)
mutating one copy doesn't touch the other
(Hashtbl.length t + Hashtbl.length t2 = 3 after fork-and-add
verifies that adds to t2 don't appear in t)
Defines a JSON-like algebraic data type:
type json =
| JNull
| JBool of bool
| JInt of int
| JStr of string
| JList of json list
Recursively serialises to a string via match-on-constructor, then
measures the length:
JList [JInt 1; JBool true; JNull; JStr 'hi'; JList [JInt 2; JInt 3]]
-> '[1,true,null,"hi",[2,3]]' length 24
Exercises:
- five-constructor ADT (one nullary, three single-arg, one list-arg)
- recursive match
- String.concat ',' (List.map to_string xs)
- string-cat with embedded escaped quotes
34 baseline programs total.
In-place Fisher-Yates shuffle using:
Random.init 42 deterministic seed
let a = Array.of_list xs
for i = n - 1 downto 1 do reverse iteration
let j = Random.int (i + 1)
let tmp = a.(i) in
a.(i) <- a.(j);
a.(j) <- tmp
done
Sum is invariant under permutation, so the test value (55 for
[1..10] = 1+2+...+10) verifies the shuffle is a valid permutation
regardless of which permutation the seed yields.
Exercises Random.init / Random.int + Array.of_list / to_list /
length / arr.(i) / arr.(i) <- v + downto loop + multi-statement
sequencing within for-body.
33 baseline programs total.
pi_leibniz.ml: Leibniz formula for pi.
pi/4 = 1 - 1/3 + 1/5 - 1/7 + ...
pi ~= 4 * sum_{k=0}^{n-1} (-1)^k / (2k+1)
For n=1000, pi ~= 3.140593. Multiply by 100 and int_of_float -> 314.
Side-quest: int_of_float was wrongly defined as identity in
iteration 94. Fixed to:
let int_of_float f =
if f < 0.0 then _float_ceil f else _float_floor f
(truncate toward zero, mirroring real OCaml's int_of_float). The
identity definition was a stub from when integer/float dispatch was
not yet split — now they're separate, the stub is wrong.
Float.to_int still uses floor since OCaml's docs say the result is
unspecified for nan / out-of-range; close enough for our scope.
32 baseline programs total.
Both take an inner predicate / comparator and walk both lists in
lockstep:
equal eq a b short-circuits on first mismatch
compare cmp a b -1 if a is a strict prefix
1 if b is
0 if both empty
otherwise first non-zero element comparison
Mirrors real OCaml's signatures.
List.equal (=) [1;2;3] [1;2;3] = true
List.equal (=) [1;2;3] [1;2;4] = false
List.compare compare [1;2;3] [1;2;4] = -1
List.compare compare [1;2] [1;2;3] = -1
List.compare compare [] [] = 0
Bool module:
equal a b = a = b
compare a b = 0 if equal, 1 if a, -1 if b (false < true)
to_string 'true' / 'false'
of_string s = s = 'true'
not_ wraps host not
to_int true=1, false=0
Option additions (take eq/cmp parameter for the inner value):
equal eq a b None=None, otherwise eq the inner values
compare cmp a b None < Some _; otherwise cmp inner
Option.equal (=) (Some 1) (Some 1) = true
Option.equal (=) (Some 1) None = false
Option.compare compare (Some 5) (Some 3) = 1
bag.ml: split a sentence on spaces, count each word in a Hashtbl,
return the maximum count via Hashtbl.fold.
count_words 'the quick brown fox jumps over the lazy dog the fox'
-> Hashtbl with 'the' = 3 as the max
-> 3
Exercises String.split_on_char + Hashtbl.find_opt/replace +
Hashtbl.fold (k v acc -> ...). Together with frequency.ml from
iter 84 we now have two Hashtbl-counting baselines exercising
slightly different idioms. 29 baseline programs total.
String additions:
equal a b = a = b
compare a b = -1 / 0 / 1 via host < / >
cat a b = a ^ b
empty = '' (constant)
Defines:
type frac = { num : int; den : int }
let rec gcd a b = if b = 0 then a else gcd b (a mod b)
let make n d = (* canonicalise: gcd-reduce and
force den > 0 *)
let add x y = make (x.num * y.den + y.num * x.den) (x.den * y.den)
let mul x y = make (x.num * y.num) (x.den * y.den)
Test:
let r = add (make 1 2) (make 1 3) in (* 5/6 *)
let s = mul (make 2 3) (make 3 4) in (* 1/2 *)
let t = add r s in (* 5/6 + 1/2 = 4/3 *)
t.num + t.den (* = 7 *)
Exercises records, recursive gcd, mod, abs, integer division (the
truncate-toward-zero semantics from iter 94 are essential here —
make would diverge from real OCaml's behaviour with float division).
28 baseline programs total.
Real OCaml's Seq.t is 'unit -> Cons of elt * Seq.t | Nil' — a lazy
thunk that lets you build infinite sequences. Ours is just a list,
which gives the right shape for everything in baseline programs that
don't rely on laziness (taking from infinite sequences would force
memory).
API: empty, cons, return, is_empty, iter, iteri, map, filter,
filter_map, fold_left, length, take, drop, append, to_list,
of_list, init, unfold.
unfold takes a step fn 'acc -> Option (elt * acc)' and threads
through until it returns None:
Seq.fold_left (+) 0
(Seq.unfold (fun n -> if n > 4 then None
else Some (n, n + 1))
1)
= 1 + 2 + 3 + 4 = 10
First baseline that exercises the functor pipeline end to end:
module IntOrd = struct
type t = int
let compare a b = a - b
end
module IntSet = Set.Make (IntOrd)
let unique_count xs =
let s = List.fold_left (fun s x -> IntSet.add x s) IntSet.empty xs in
IntSet.cardinal s
Counts unique elements in [3;1;4;1;5;9;2;6;5;3;5;8;9;7;9]:
{1,2,3,4,5,6,7,8,9} -> 9
The input has 15 elements with 9 unique values. The 'type t = int'
declaration in IntOrd is required by real OCaml; OCaml-on-SX is
dynamic and would accept it without, but we include it for source
fidelity. 27 baseline programs total.
Functors were already wired through ocaml-make-functor in eval.sx
(curried host closure consuming module dicts) but had no explicit
tests for the user-defined Ord application path. This commit adds
three smoke tests that confirm:
module IntOrd = struct let compare a b = a - b end
module S = Set.Make (IntOrd)
S.elements (fold-add [5;1;3;1;5]) sums to 9 (dedupe + sort)
S.mem 2 (S.add 1 (S.add 2 (S.add 3 S.empty))) = true
M.cardinal (M.add 1 'a' (M.add 2 'b' M.empty)) = 2
The Ord parameter is properly threaded through the functor body —
elements are sorted in compare order and dedupe works.
Three parser changes:
1. at-app-start? returns true on op '~' or '?' so the app loop
keeps consuming labeled args.
2. The app arg parser handles:
~name:VAL drop label, parse VAL as the arg
?name:VAL same
~name punning -- treat as (:var name)
?name same
3. try-consume-param! drops '~' or '?' and treats the following
ident as a regular positional param name.
Caveats:
- Order in the call must match definition order; we don't reorder
by label name.
- Optional args don't auto-wrap in Some, so the function body sees
the raw value for ?x:V.
Lets us write idiomatic-looking OCaml even though the runtime is
positional underneath:
let f ~x ~y = x + y in f ~x:3 ~y:7 = 10
let x = 4 in let y = 5 in f ~x ~y = 20 (punning)
let f ?x ~y = x + y in f ?x:1 ~y:2 = 3
User-implemented mergesort that exercises features added across the
last few iterations:
let rec split lst = match lst with
| x :: y :: rest ->
let (a, b) = split rest in (* iter 98 let-tuple destruct *)
(x :: a, y :: b)
| ...
let rec merge xs ys = match xs with
| x :: xs' ->
match ys with (* nested match-in-match *)
| y :: ys' -> ...
...
List.fold_left (+) 0 (sort [...]) (* iter 89 (op) section *)
Sum of [3;1;4;1;5;9;2;6;5;3;5] = 44 regardless of order, so the
result is also a smoke test of the implementation correctness — if
merge_sort drops or duplicates an element the sum diverges. 26
baseline programs total.