01 — type inference
No annotations anywhere; the compiler derives the type — and rejects what would break it.
fun len [] = 0
| len (_::xs) = 1 + len xs
(* val len = fn : 'a list -> int *)§ 02 — Eras · 1973
types inferred, not written — safety, silently
Left: era context, features, and the 101 hello. Right: 3 core concepts, 3 deeper 201 lessons, the factorial specimen, and where ML lives today — each column scrolls on its own.
Proof tactics had to be correct before they ran. Milner made the compiler derive the types — so errors surfaced before execution.
An academic home and a small ecosystem — for decades, the best language you weren’t allowed to deploy.
Session one is the smallest program that still counts as speaking ML: what every tutorial prints first, in the dialect that mattered when the language arrived (1973).
print "Hello, world\n";101First contact — hello world and 3 ideas that define how ML reads. 201 — 3 lessons on mechanisms you meet after the factorial specimen: the sharp edges practitioners actually use.
3 fragments, each naming a habit of mind in ML. Read the note, then the code — together they are the 101 in one sitting.
01 — type inference
No annotations anywhere; the compiler derives the type — and rejects what would break it.
fun len [] = 0
| len (_::xs) = 1 + len xs
(* val len = fn : 'a list -> int *)02 — pattern matching
Define a function by cases on the shape of data; missing a case is a warning.
fun sum [] = 0
| sum (x::xs) = x + sum xs03 — polymorphism for free
Hindley–Milner inference makes generic code the default, not a feature.
fun id x = x
(* val id = fn : 'a -> 'a *)The 201 assumes you have read the 101 and the specimen. Each lesson is one mechanism — what it is for, how it looks in code, and the trap or payoff that makes ML distinctive at scale.
Algebraic types give you variants that carry their own data, and pattern matching turns them into readable, total functions — the standard for “safe” recursion.
datatype 'a tree = Leaf | Node of 'a tree * 'a * 'a tree
fun size Leaf = 1
| size (Node (l, _, r)) = size l + size r;Functors are modules with parameters: a dictionary structure once, instantiated for any key type — the ancestor of Rust’s traits and C++’s templates.
signature ORD = type t
signature MAP = functor F (ORD) : sig
type map
val empty : map
val insert : t * map -> map
endEvery function takes one argument and returns a function; partial application is ordinary, not a trick — which is why ML code often reads as pipelines.
fun add x y = x + y
val inc = add 1 (* still a function *)The inferred type: you write the function, the language deduces the type — and refuses the program if the two disagree. Milner’s algorithm made safety silent.
fun map f [] = []
| map f (x::xs) = f x :: map f xs;
(* val map = fn : ('a -> 'b) -> 'a list -> 'b list *)installbrew install smlnj
runsml fact.sml
Its type system quietly moved into Scala, Swift, Rust, TypeScript and every modern “algebraic” type you meet.