Use arrow keys or buttons to navigate. Press 'S' to reveal steps.
1 / 22
Why OCaml for Theory?
Functional programming and formal languages are deeply connected
The Connections
Pattern matching = state transitions in automata
Recursive types = grammar productions in CFGs
Immutability = mathematical functions (same input, same output)
Recursive functions = recursive descent parsing
Algebraic data types = abstract syntax trees
Theory Concept OCaml Feature
─────────────── ─────────────
DFA transitions --> match state, symbol with
CFG productions --> type expr = Add of expr * expr
Parse trees --> recursive data types
Language membership -> recursive functions
Math functions --> pure functions (no side effects)
Key Idea
OCaml lets you write code that looks like the math you see in theory class. A DFA simulator in OCaml reads almost identically to the formal definition (Q, Σ, δ, q0, F).
Analogy: Why Not Java/Python?
Writing a DFA simulator in Java is like translating Shakespeare into emojis -- it works, but the beauty of the original structure is lost. OCaml preserves the mathematical elegance.
OCaml does NOT auto-convert between int and float. Use + for ints, +. for floats. 2 + 3 works, but 2 + 3.0 is a type error! Use float_of_int to convert.
# 2 + 3;;
- : int = 5
# 2.0 +. 3.0;;
- : float = 5.0
# 2 + 3.0;; (* TYPE ERROR! *)
Error: This expression has type float but an expression of type int was expected
# float_of_int 2 +. 3.0;;
- : float = 5.0
4 / 22
Let Bindings
Naming values -- not assigning variables
Top-Level Bindings
# let x = 5;;
val x : int = 5
# let greeting = "hello";;
val greeting : string = "hello"
# let is_even n = (n mod 2 = 0);;
val is_even : int -> bool = <fun>
# x + 10;;
- : int = 15
Local Bindings with let...in
# let area r =
let pi = 3.14159 in
let r_sq = r *. r in
pi *. r_sq;;
val area : float -> float = <fun>
# area 5.0;;
- : float = 78.53975
Analogy: Mathematical Definitions
In math, when we write "let x = 5," we mean x IS 5. We don't mean "put 5 in the box labeled x." There is no box. There is no mutation. let in OCaml works the same way.
Immutability by Default
Once you bind a name, it cannot change. This is like how in math, once you define a variable in a proof, it stays fixed.
(* You CAN shadow a binding: *)
# let x = 5;;
val x : int = 5
# let x = 10;; (* new binding, old x is gone *)
val x : int = 10
(* But you CANNOT mutate: *)
# x = 20;; (* This is equality CHECK, not assignment! *)
- : bool = false
Java/Python Gotcha
= is comparison in OCaml, not assignment. let is how you bind names. There is no x = x + 1.
5 / 22
Functions
First-class citizens -- functions are values like any other
Defining Functions
(* Named function *)
# let increment x = x + 1;;
val increment : int -> int = <fun>
(* Multi-parameter function *)
# let add x y = x + y;;
val add : int -> int -> int = <fun>
(* Anonymous function (lambda) *)
# (fun x -> x + 1) 5;;
- : int = 6
# let double = fun x -> x * 2;;
val double : int -> int = <fun>
Partial Application
# let add x y = x + y;;
val add : int -> int -> int = <fun>
# let add5 = add 5;;
val add5 : int -> int = <fun>
# add5 3;;
- : int = 8
Currying: How Multi-Arg Functions Really Work
add : int -> int -> int
This REALLY means:
add : int -> (int -> int)
"A function that takes an int and
returns a function that takes an int
and returns an int"
┌─────┐ ┌─────┐ ┌─────┐
│ 5 │───>│ add │───>│ f │
└─────┘ │ │ │ (adds 5│
└─────┘ │to arg) │
└───┬───┘
┌─────┐ │ ┌─────┐
│ 3 │───┘──>│ 8 │
└─────┘ └─────┘
Key Idea
Every function in OCaml takes exactly one argument. Multi-argument functions are syntactic sugar for curried chains. let add x y = ... is really let add = fun x -> fun y -> ...
6 / 22
Type Inference
OCaml figures out the types so you don't have to write them
The Compiler as Detective
# let mystery x y =
if x > 0 then y ^ "!" else y;;
(* OCaml deduces:
x > 0 --> x must be int
y ^ "!" --> y must be string
result --> string
*)
val mystery : int -> string -> string = <fun>
# let pick_first (a, b) = a;;
val pick_first : 'a * 'b -> 'a = <fun>
(* 'a and 'b are TYPE VARIABLES
This works for ANY types!
pick_first (1, "hi") --> 1
pick_first (true, 42) --> true *)
Polymorphism (Generics)
# let identity x = x;;
val identity : 'a -> 'a = <fun>
(* 'a means "any type" -- like
Java's <T> but automatic *)
# identity 42;;
- : int = 42
# identity "hello";;
- : string = "hello"
Analogy: Detective Work
Type inference is like solving a mystery. The compiler sees clues: x + 1 means x is an int. y ^ "!" means y is a string. It pieces together evidence to determine every type, without you writing a single annotation.
Hindley-Milner Type System
OCaml uses the Hindley-Milner type inference algorithm. It can infer the most general type for any expression. This is a real CS theory result -- the algorithm is based on unification, the same technique used in logic programming and automated theorem proving.
You CAN Add Type Annotations
If you want to be explicit (or get better error messages):
# let add (x : int) (y : int) : int =
x + y;;
val add : int -> int -> int = <fun>
7 / 22
If-Then-Else as Expressions
Everything in OCaml is an expression that returns a value
Expressions, Not Statements
(* if-then-else RETURNS a value *)
# let abs x =
if x >= 0 then x else -x;;
val abs : int -> int = <fun>
# let max a b =
if a > b then a else b;;
val max : 'a -> 'a -> 'a = <fun>
(* You can use it inline *)
# let msg = "You " ^
(if true then "win" else "lose");;
val msg : string = "You win"
(* Nested if-then-else *)
# let classify n =
if n > 0 then "positive"
else if n < 0 then "negative"
else "zero";;
val classify : int -> string = <fun>
Key Idea: Both Branches Must Have the Same Type
Since if-then-else is an expression that produces a value, both branches must produce the same type. This is like how a mathematical piecewise function must have a consistent codomain.
Common Error: Type Mismatch
# if true then 42 else "hello";;
Error: This expression has type string
but an expression of type int was expected
(* "then" branch is int, "else" is string
-- OCaml says NO! *)
Gotcha: if Without else
(* Only valid when the result is unit *)
# if true then print_endline "hi";;
hi
- : unit = ()
(* This FAILS -- what would the else return? *)
# if true then 42;;
Error: This expression has type int
but an expression of type unit was expected
8 / 22
Recursion
No loops needed -- recursion is the way
The rec Keyword
(* MUST use "let rec" for recursion *)
# let rec factorial n =
if n <= 0 then 1
else n * factorial (n - 1);;
val factorial : int -> int = <fun>
# factorial 5;;
- : int = 120
(* Fibonacci *)
# let rec fib n =
if n <= 1 then n
else fib (n-1) + fib (n-2);;
val fib : int -> int = <fun>
# fib 10;;
- : int = 55
Tail Recursion (Optimized)
# let factorial_tail n =
let rec aux acc n =
if n <= 0 then acc
else aux (acc * n) (n - 1)
in aux 1 n;;
val factorial_tail : int -> int = <fun>
If you write let factorial n = ... factorial (n-1) without rec, OCaml will look for a previously defined function called factorial, not the one you're defining. Always use let rec for recursive functions!
Analogy: Recursion = Induction
Recursive functions mirror structural induction proofs from theory class. Base case = base case. Recursive call = inductive step. If you can write an inductive proof, you can write a recursive function.
9 / 22
Tuples and Records
Grouping data together
Tuples -- Quick Grouping
# (1, "hello", true);;
- : int * string * bool = (1, "hello", true)
# let point = (3.0, 4.0);;
val point : float * float = (3.0, 4.0)
(* Access via pattern matching *)
# let (x, y) = point;;
val x : float = 3.
val y : float = 4.
# let distance (x1,y1) (x2,y2) =
let dx = x2 -. x1 in
let dy = y2 -. y1 in
sqrt (dx *. dx +. dy *. dy);;
val distance :
float * float -> float * float -> float
= <fun>
# distance (0.0, 0.0) (3.0, 4.0);;
- : float = 5.
Records -- Named Fields
(* Define a record type first *)
# type person = {
name : string;
age : int;
};;
type person = { name : string; age : int; }
(* Create a record *)
# let alice = { name = "Alice"; age = 20 };;
val alice : person = {name = "Alice"; age = 20}
(* Access fields *)
# alice.name;;
- : string = "Alice"
(* Pattern match on records *)
# let greet { name; age } =
Printf.printf "%s is %d\n" name age;;
val greet : person -> unit = <fun>
Key Idea: The * in Tuple Types
int * string * bool uses * because tuples are Cartesian products of types -- the same cross-product from set theory!
10 / 22
Lists
The fundamental data structure of functional programming
Creating Lists
# [1; 2; 3];;
- : int list = [1; 2; 3]
# ["hello"; "world"];;
- : string list = ["hello"; "world"]
# [];;
- : 'a list = [] (* empty list *)
(* :: is "cons" -- prepend an element *)
# 1 :: [2; 3];;
- : int list = [1; 2; 3]
# 1 :: 2 :: 3 :: [];;
- : int list = [1; 2; 3]
(* @ is append -- concatenate lists *)
# [1; 2] @ [3; 4];;
- : int list = [1; 2; 3; 4]
Gotcha: Semicolons, Not Commas!
[1; 2; 3] is a list of three ints. [1, 2, 3] is a list of ONE tuple (1,2,3). This trips up everyone coming from Python/Java.
Lists are Linked Lists
[1; 2; 3] is really:
┌───┬───┐ ┌───┬───┐ ┌───┬───┐
│ 1 │ ─┼──>│ 2 │ ─┼──>│ 3 │ / │
└───┴───┘ └───┴───┘ └───┴───┘
1 :: (2 :: (3 :: []))
cons is O(1), append is O(n)
head is O(1), indexing is O(n)
Key Idea: All Elements Must Be the Same Type
[1; "hello"; true] is a type error. Unlike Python lists, OCaml lists are homogeneous. Think of it as a formal language: the list type is the Kleene star of a single alphabet symbol.
(* List functions *)
# List.length [1; 2; 3];;
- : int = 3
# List.hd [1; 2; 3];;
- : int = 1
# List.tl [1; 2; 3];;
- : int list = [2; 3]
# List.rev [1; 2; 3];;
- : int list = [3; 2; 1]
11 / 22
Pattern Matching (Part 1)
The most powerful feature in OCaml -- your new best friend
Basic Syntax
# let describe_number n =
match n with
| 0 -> "zero"
| 1 -> "one"
| 2 -> "two"
| _ -> "many";;
val describe_number : int -> string = <fun>
# describe_number 1;;
- : string = "one"
# describe_number 99;;
- : string = "many"
Matching on Tuples
# let and_gate a b =
match (a, b) with
| (true, true) -> true
| (true, false) -> false
| (false, true) -> false
| (false, false) -> false;;
val and_gate : bool -> bool -> bool = <fun>
(* Shorter with wildcards: *)
# let and_gate a b =
match (a, b) with
| (true, true) -> true
| _ -> false;;
Analogy: DFA Transition Table!
Pattern matching is structurally identical to a DFA's transition function δ(q, a):
OCaml warns you if your patterns don't cover all cases. This is like checking that your DFA transition function is total -- every (state, symbol) pair must have a transition!
# let bad n = match n with
| 0 -> "zero"
| 1 -> "one";;
Warning: this pattern-matching is
not exhaustive.
12 / 22
Pattern Matching (Part 2)
Matching on lists -- recursive list processing
List Patterns
# let describe_list lst =
match lst with
| [] -> "empty"
| [x] -> "one element"
| [x; y] -> "two elements"
| x :: rest -> "many elements";;
val describe_list : 'a list -> string = <fun>
(* x :: rest destructures the list:
x = first element (head)
rest = remaining list (tail) *)
Recursive List Processing
# let rec length lst =
match lst with
| [] -> 0
| _ :: tl -> 1 + length tl;;
val length : 'a list -> int = <fun>
# let rec sum lst =
match lst with
| [] -> 0
| x :: tl -> x + sum tl;;
val sum : int list -> int = <fun>
# sum [1; 2; 3; 4];;
- : int = 10
Building Classic Functions
# let rec map f lst =
match lst with
| [] -> []
| x :: tl -> (f x) :: (map f tl);;
val map : ('a -> 'b) -> 'a list -> 'b list
= <fun>
# map (fun x -> x * 2) [1; 2; 3];;
- : int list = [2; 4; 6]
# let rec filter pred lst =
match lst with
| [] -> []
| x :: tl ->
if pred x then x :: filter pred tl
else filter pred tl;;
val filter : ('a -> bool) -> 'a list -> 'a list
= <fun>
# filter (fun x -> x > 2) [1; 2; 3; 4];;
- : int list = [3; 4]
The Pattern (pun intended)
Every recursive list function follows the same template: base case for [], recursive case splitting into head :: tail. This mirrors structural induction on list length!
13 / 22
Algebraic Data Types (Variants)
Custom types with constructors -- the heart of OCaml
Defining Variant Types
# type shape =
| Circle of float
| Rectangle of float * float
| Triangle of float * float * float;;
type shape =
Circle of float
| Rectangle of float * float
| Triangle of float * float * float
# let s1 = Circle 5.0;;
val s1 : shape = Circle 5.
# let s2 = Rectangle (3.0, 4.0);;
val s2 : shape = Rectangle (3., 4.)
Using Variants with Pattern Matching
# let area s =
match s with
| Circle r -> 3.14159 *. r *. r
| Rectangle (w, h) -> w *. h
| Triangle (a, b, c) ->
let s = (a +. b +. c) /. 2.0 in
sqrt (s *.(s-.a)*.(s-.b)*.(s-.c));;
val area : shape -> float = <fun>
# area (Circle 5.0);;
- : float = 78.53975
Analogy: Grammar Productions!
Each variant constructor is like a production rule in a grammar:
Grammar: OCaml:
────────── ──────
shape -> Circle float type shape =
| Rect float float | Circle of float
| Tri float float | Rectangle of float * float
float | Triangle of float * float
* float
Each "|" is an alternative production.
The "of ..." is the right-hand side
specifying what data the constructor
carries.
Key Idea: Sum Types
Variants are called sum types because a shape is a Circle OR a Rectangle OR a Triangle. The total possibilities are the SUM of all constructors. (Tuples are product types -- AND.) This is algebraic data types = algebra on types!
(* Simple enum-style variant *)
# type color = Red | Green | Blue;;
# type day = Mon|Tue|Wed|Thu|Fri|Sat|Sun;;
14 / 22
Recursive Types
Types that refer to themselves -- just like recursive grammars
Binary Tree
# type tree =
| Leaf
| Node of tree * int * tree;;
# let my_tree =
Node (
Node (Leaf, 1, Leaf),
3,
Node (Leaf, 5, Leaf)
);;
(*
3
/ \
1 5
/ \ / \
. . . .
*)
# let rec tree_sum t =
match t with
| Leaf -> 0
| Node (left, v, right) ->
tree_sum left + v + tree_sum right;;
val tree_sum : tree -> int = <fun>
# tree_sum my_tree;;
- : int = 9
Arithmetic Expression AST
# type expr =
| Num of int
| Add of expr * expr
| Mul of expr * expr;;
(* This IS the CFG from class!
E -> n | E + E | E * E *)
# let e = Add (Num 1, Mul (Num 2, Num 3));;
(* Represents: 1 + (2 * 3) *)
# let rec eval e =
match e with
| Num n -> n
| Add (l, r) -> eval l + eval r
| Mul (l, r) -> eval l * eval r;;
val eval : expr -> int = <fun>
# eval (Add (Num 1, Mul (Num 2, Num 3)));;
- : int = 7
Key Idea: Types = Grammars, Values = Parse Trees
The type expr definition IS a context-free grammar. Each value of type expr IS a parse tree. The eval function IS a recursive descent evaluator walking the parse tree. This is the exact same structure from your theory course!
15 / 22
The Option Type
Safe handling of "maybe there's no value" -- no null pointer exceptions
Definition
(* Built into OCaml: *)
type 'a option =
| None
| Some of 'a
# Some 42;;
- : int option = Some 42
# None;;
- : 'a option = None
# Some "hello";;
- : string option = Some "hello"
Using Options
# let safe_div a b =
if b = 0 then None
else Some (a / b);;
val safe_div : int -> int -> int option = <fun>
# safe_div 10 3;;
- : int option = Some 3
# safe_div 10 0;;
- : int option = None
Pattern Matching on Options
# let describe_result opt =
match opt with
| None -> "no result"
| Some v -> "got: " ^ string_of_int v;;
val describe_result : int option -> string
= <fun>
(* Find in a list *)
# let rec find_first pred lst =
match lst with
| [] -> None
| x :: tl ->
if pred x then Some x
else find_first pred tl;;
val find_first :
('a -> bool) -> 'a list -> 'a option = <fun>
# find_first (fun x -> x > 3) [1;2;3;4;5];;
- : int option = Some 4
# find_first (fun x -> x > 10) [1;2;3];;
- : int option = None
Analogy: Java's null vs OCaml's Option
In Java, any reference can be null and you only find out at runtime (NullPointerException). In OCaml, if a value might be absent, the type says option, and the compiler forces you to handle the None case. It's like the difference between an informal proof ("trust me, it's not null") and a formal proof (the type system guarantees it).
16 / 22
Higher-Order Functions
Functions that take functions as arguments or return them
The Big Three: map, filter, fold
(* map: transform every element *)
# List.map (fun x -> x * x) [1;2;3;4];;
- : int list = [1; 4; 9; 16]
(* filter: keep elements matching predicate *)
# List.filter (fun x -> x mod 2 = 0) [1;2;3;4];;
- : int list = [2; 4]
(* fold_left: accumulate a result *)
# List.fold_left (fun acc x -> acc + x)
0 [1;2;3;4];;
- : int = 10
Function Composition
# let compose f g x = f (g x);;
val compose :
('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun>
# let double_then_add1 =
compose (fun x -> x+1) (fun x -> x*2);;
val double_then_add1 : int -> int = <fun>
# double_then_add1 5;;
- : int = 11 (* 5*2=10, 10+1=11 *)
Type Signatures Tell the Story
List.map :
('a -> 'b) -> 'a list -> 'b list
──────── ─────── ───────
function input output
to apply list list
List.filter :
('a -> bool) -> 'a list -> 'a list
────────── ─────── ───────
predicate input filtered
(keep if list list
true)
List.fold_left :
('a -> 'b -> 'a) -> 'a -> 'b list -> 'a
────────────── ─── ─────── ──
accumulator fn init input final
value list result
Key Idea
Higher-order functions let you separate the what from the how. map says "transform each element" -- you just provide the transformation. This is abstraction at its finest, and it's why functional code is often shorter than imperative code.
17 / 22
Map and Filter Visualized
Data pipelines -- transform and select
Map: Transform Every Element
List.map (fun x -> x * x) [1; 2; 3; 4; 5]
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5 │ input list
└─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘
│ │ │ │ │
v v v v v
[x*x] [x*x] [x*x] [x*x] [x*x] apply f to each
│ │ │ │ │
v v v v v
┌───┐ ┌───┐ ┌───┐ ┌────┐ ┌────┐
│ 1 │ │ 4 │ │ 9 │ │ 16 │ │ 25 │ output list
└───┘ └───┘ └───┘ └────┘ └────┘
Filter: Keep Only Matching Elements
List.filter (fun x -> x mod 2 = 0) [1; 2; 3; 4; 5; 6]
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5 │ │ 6 │ input list
└─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘ └─┬─┘
│ │ │ │ │ │
even? even? even? even? even? even? test predicate
│ │ │ │ │ │
NO YES NO YES NO YES
x │ x │ x │ drop / keep
v v v
┌───┐ ┌───┐ ┌───┐
│ 2 │ │ 4 │ │ 6 │ output list
└───┘ └───┘ └───┘
Chaining: The Pipeline Pattern
You can chain map and filter to build data processing pipelines. This is just function composition!
(* "Get the squares of all even numbers in the list" *)
# [1;2;3;4;5;6;7;8;9;10]
|> List.filter (fun x -> x mod 2 = 0) (* keep evens: [2;4;6;8;10] *)
|> List.map (fun x -> x * x);; (* square: [4;16;36;64;100] *)
- : int list = [4; 16; 36; 64; 100]
(* |> is the "pipe" operator: x |> f means f x *)
18 / 22
Fold: The Universal List Function
Every list function can be written as a fold
fold_left: Accumulate Left to Right
List.fold_left f init [a; b; c]
Computes: f (f (f init a) b) c
init
│
v
┌──────┐
│ f a│──> result1
└──────┘
│
v
┌──────┐
│ f b│──> result2
└──────┘
│
v
┌──────┐
│ f c│──> FINAL
└──────┘
(* Sum *)
# let sum lst =
List.fold_left (+) 0 lst;;
(* Length *)
# let length lst =
List.fold_left
(fun acc _ -> acc + 1) 0 lst;;
(* Reverse *)
# let rev lst =
List.fold_left
(fun acc x -> x :: acc) [] lst;;
(* Map via fold *)
# let map f lst =
List.fold_right
(fun x acc -> f x :: acc) lst [];;
(* Filter via fold *)
# let filter pred lst =
List.fold_right
(fun x acc ->
if pred x then x :: acc else acc)
lst [];;
(* Flatten *)
# let flatten lst =
List.fold_right (@) lst [];;
Key Idea
fold is the universal list eliminator. Any function that consumes a list and produces a single result can be expressed as a fold. In theory terms: fold captures the catamorphism over the list type -- the unique function determined by the algebra of its constructors ([] and ::).
19 / 22
Connections to Theory
A complete DFA simulator in OCaml -- tying it all together
(* Define states *)
type state = Q0 | Q1 | Q2
(* Transition function -- pattern matching! *)
let delta state symbol =
match (state, symbol) with
| (Q0, 'a') -> Q1
| (Q0, 'b') -> Q0
| (Q1, 'a') -> Q1
| (Q1, 'b') -> Q2
| (Q2, 'a') -> Q1
| (Q2, 'b') -> Q0
| _ -> Q0 (* catch-all *)
(* Accept states *)
let is_accepting state =
match state with
| Q2 -> true
| _ -> false
(* Run the DFA on an input string *)
let run_dfa input =
let chars = List.init (String.length input) (String.get input) in (* string -> char list *)
let final_state = List.fold_left delta Q0 chars in (* process all symbols *)
is_accepting final_state (* check acceptance *)
(* Test it! *)
# run_dfa "ab";; - : bool = true (* ends in "ab" *)
# run_dfa "aab";; - : bool = true (* ends in "ab" *)
# run_dfa "abb";; - : bool = false (* ends in "b", not "ab" *)
# run_dfa "bababab";; - : bool = true (* ends in "ab" *)
# run_dfa "";; - : bool = false (* empty string *)
Key Idea: The Code IS the Math
delta is literally δ. fold_left delta Q0 chars is literally δ*(q0, w). is_accepting final_state is literally "is δ*(q0, w) in F?" The OCaml code maps 1:1 to the formal definition. This is why OCaml is the natural language for automata theory.
20 / 22
Practical Tips & Common Errors
Java/Python habits that will trip you up in OCaml
Common Errors and Fixes
1. Forgetting rec
let f x = ... f x --> "Unbound value f" Fix: let rec f x = ...
2. Semicolons vs Double-Semicolons
;; = end of expression in REPL only ; = sequence two unit expressions
In .ml files, you rarely need ;;
3. Type Mismatch Errors
2 + 3.0 --> Error! Use 2.0 +. 3.0 if x then 1 else "no" --> Error! Branches must match.
4. Parentheses for Function Arguments
f g x means "call f with two args: g and x" f (g x) means "call g on x, pass result to f"
Java/Python to OCaml Translation
Java/Python OCaml
─────────── ─────
int x = 5; let x = 5
x = x + 1; (* impossible! use recursion *)
for/while loop let rec ... (recursion)
null None (option type)
x == y x = y
x != y x <> y
arr[i] List.nth lst i
arr.length List.length lst
return val (just write the expression)
void unit
System.out.println print_endline
ArrayList list (but immutable)
class Foo { ... } type foo = ...
Golden Rules
No mutation: use recursion, not loops
No return: the last expression IS the result
No null: use option type
Read the types: they tell you everything
When in doubt, pattern match
21 / 22
Summary & Cheat Sheet
Quick reference for everything you need
Syntax Quick Reference
(* Values and bindings *)
let x = 42
let f x y = x + y
let f = fun x -> fun y -> x + y
(* Local binding *)
let x = 5 in x + 1
(* Recursion *)
let rec f n = if n=0 then 1 else n * f(n-1)
(* Pattern matching *)
match x with
| pattern1 -> expr1
| pattern2 -> expr2
| _ -> default
(* Type definitions *)
type color = Red | Green | Blue
type shape = Circle of float
| Rect of float * float
type 'a tree = Leaf
| Node of 'a tree * 'a * 'a tree
(* Records *)
type person = { name: string; age: int }
let p = { name = "Alice"; age = 20 }
(* Option *)
type 'a option = None | Some of 'a
Common List Operations
[] (* empty list *)
x :: lst (* prepend *)
lst1 @ lst2 (* append *)
List.length lst (* length *)
List.hd lst (* first element *)
List.tl lst (* rest of list *)
List.rev lst (* reverse *)
List.map f lst (* transform each *)
List.filter p lst (* keep matching *)
List.fold_left f i l (* accumulate L->R *)
List.fold_right f l i (* accumulate R->L *)
List.mem x lst (* membership check *)
List.nth lst i (* index access *)
List.sort cmp lst (* sort *)
Theory-to-OCaml Mental Model
Theory OCaml
────── ─────
δ(q, a) = q' match (q, a) with ...
δ*(q0, w) fold_left delta q0 w
CFG production variant constructor
Parse tree recursive data type
Language L bool-returning function
w ∈ L accepts w = true
You're Ready!
You now have the OCaml toolkit to implement DFAs, NFAs, CFGs, parsers, and evaluators for your theory course. The key insight: the code mirrors the math. If you understand the formal definition, you can write the OCaml. Start with the types, then pattern match your way through.
22
We'd Love Your Feedback!
Help us improve these slides -- your responses go directly to a Google Sheet
Thank you!
Your feedback has been recorded. It helps us make better learning materials.
Submission failed
Please check the Google Sheet connection and try again.
About these slides
This slide deck was generated with the help of Claude (Anthropic's AI assistant) and reviewed by your instructor. We're curious what you think about AI-assisted teaching materials!