OCaml Crash Course
This course uses OCaml for all exercises and labs. Before diving into the warm-up exercises, let's build intuition for the key OCaml features you'll use throughout: pattern matching for deconstructing data, algebraic types for modeling domains, and functors for writing generic analysis frameworks.
Why OCaml for Program Analysis?
OCaml is the language of choice for compiler and analysis tool development. Its type system catches bugs at compile time, pattern matching makes AST traversal concise, and algebraic data types let you model complex structures naturally. Many real-world tools — including parts of the Rust compiler, Flow (Facebook's JS type checker), and Infer (Facebook's static analyzer) — are written in OCaml.
Type Safety
The compiler catches missing cases, type mismatches, and unhandled variants at compile time — not at runtime.
Pattern Matching
Destructure complex data in one step. The compiler warns if you miss a case — essential for handling every AST node type.
Modules & Functors
Parameterize code over abstract types. Write one analyzer framework, plug in different domains — sign, interval, taint.
Pattern Matching
Pattern matching is OCaml's superpower. Instead of chains of if-else or switch statements, you destructure values directly. The compiler checks that every case is covered — if you forget a variant, you get a warning.
let describe x = match x with | 0 -> "zero" | 1 -> "one" | n when n > 0 -> "positive" | _ -> "negative"
describe 0→"zero"describe 42→"positive"describe (-3)→"negative"Algebraic Data Types
Algebraic data types (ADTs) let you define types with variants — each variant can carry different data. This is exactly how ASTs, lattice elements, and analysis results are modeled throughout this course.
(* Define the type *) type expr = | Num of int | Add of expr * expr | Mul of expr * expr (* Build a tree: (2 + 3) * 4 *) let tree = Mul (Add (Num 2, Num 3), Num 4) (* Evaluate recursively *) let rec eval = function | Num n -> n | Add (a, b) -> eval a + eval b | Mul (a, b) -> eval a * eval b
This Pattern Is Everywhere
The same ADT + recursive function pattern appears in every module: AST nodes (M2), CFG blocks (M3), lattice elements (M4), taint values (M5), and finding types (M6). Master it here and it unlocks the entire course.
Exhaustiveness Checking
If you add a Sub variant to expr but forget to handle it in eval, OCaml gives a compile-time warning. This catches bugs that would be silent runtime errors in Python or JavaScript.
Modules & Functors
OCaml modules are like interfaces on steroids. A module type defines a contract (what types and functions must exist), and a functor is a module parameterized by another module — essentially a “function from module to module.” This is how we build domain-generic analyzers in M4-M6.
(* 1. Define the contract *)
module type LATTICE = sig
type t
val bottom : t
val top : t
val join : t -> t -> t
val meet : t -> t -> t
end
(* 2. Implement it for signs *)
module SignLattice : LATTICE = struct
type t = Bot | Neg | Zero | Pos | Top
let bottom = Bot
let top = Top
let join a b = match a, b with
| Bot, x | x, Bot -> x
| x, y when x = y -> x
| _ -> Top
let meet a b = match a, b with
| Top, x | x, Top -> x
| x, y when x = y -> x
| _ -> Bot
end
(* 3. Build a generic analyzer *)
module MakeAnalyzer (L : LATTICE) = struct
let analyze env = (* works with ANY lattice! *)
L.join env L.bottom
endWhy This Matters
In Module 4, you'll implement the ABSTRACT_DOMAIN module type and use the MakeEnv functor to build environments for any domain. In Module 5, the taint lattice plugs into the same framework. The functor pattern means you write the analysis engine once and reuse it for every domain — exactly how real tools work.
Ready to Practice?
You now have a mental model of the OCaml features you'll use throughout this course: pattern matching for AST traversal, algebraic types for modeling domains, and functors for building generic analysis frameworks. Time to practice.