A little book of dependent types
The Little Leaner
Dependent Types and Proofs in Lean 4, One Question at a Time
Volodymyr Pavlyshyn
This book keeps the reasons. It uses Lean 4, a programming language in which a claim such as "the frog eats flies" is a type, and a reason for the claim, a proof, is a thing of that type. Lean checks proofs the way it checks programs: before anything runs, and without trusting anyone. If Lean accepts a proof, the claim holds.
A small book about a big idea: a type can say what a program promises, and a proof is a program that keeps the promise.
The Little Leaner teaches dependent types and proofs in Lean 4, one question and one answer at a time, in the style of The Little Schemer. Cover the right-hand column, read the question, answer it, then look.
Start with a pond and the types of its critters. Prove who eats whom. Climb through and, or, not, for every and there is; numbers, equality and induction; lists, and vectors whose types know their length. Finish with a little language whose programs cannot go wrong, and a compiler to a stack machine that is proved to keep their meaning.
- ten short chapters, about 750 questions, twelve Maxims
- 39 graded exercises with room to write, and Notes pages to draw on
- every program, output, and proof goal checked by Lean 4.35.0
- no logic course, no mathematics, no Mathlib assumed
The sequel to The Little Elpier, which is not required.
10 chapters, 760 questions, 12 Maxims, and graded exercises at the end of every chapter, each with a solution that Lean checks.
From a pond to a proved compiler
The book starts with eight critters in a pond and a table of who eats whom. It ends with a little programming language whose bad programs cannot be written, and a compiler for it that Lean has checked keeps the meaning of every program. Each step uses the one before:
- Claims are types
"The frog eats flies" becomes a type, and a proof is a thing of that type, checked like any program. - Logic, built from constructors
And, or, if, not, for every, and there is: each one a type, with rules for proving and using it. - Induction and equality
Proofs about numbers and lists that follow the recursion, and what=really means. - Types that mention values
Vectors that know their length, evidence you can carry, and questions Lean decides for us. - A language that cannot go wrong
A typed expression language, a stack machine whose stack cannot run dry, and a correctness proof.
No logic course and no mathematics are assumed, only some programming. Every program, output and proof goal printed in the book is checked by Lean 4, so the book cannot quietly drift from what Lean says.
Get the book
The Little Leaner is on Leanpub as a PDF and an EPUB. Buy it once, and every future update is yours too. A book like this takes months to write; buying it pays for the work, and for the next book.
Try it first
Read two sections of Chapter 2 below, and run every program in the book from the free companion code.
Pavlyshyn, V. (2026).
The Little Leaner: Dependent Types and Proofs in Lean 4, One Question at a Time. https://volland.github.io/little-leaner/
Read it the way the book reads
Each page is two columns: a question on the left, its answer on the right. Cover the answers, try each question, then look. Here are two sections of Chapter 2, where the first proofs appear.
The First Theorem
How do we tell Lean that the frog eats flies, and keep the proof?
We write a theorem:
theorem frog_eats_fly : Eats frog fly := by
exact Eats.frog_fly
Lean accepts it and prints nothing.
How do we read it?
theorem gives the proof a name, frog_eats_fly. After the colon comes the claim. After := comes the proof.
And by?
by starts a list of tactics. A tactic is a small step that builds part of a proof for us. This proof has one step.
What is the goal before the first tactic?
⊢ Eats frog fly
A goal is what is still missing. Read ⊢ as "show that". We must show that the frog eats flies.
Where does that goal come from?
From the claim after the colon. Before any tactic runs, the whole claim is missing.
What does exact Eats.frog_fly do?
It hands Lean a thing and says "this has exactly the type of the goal". Lean checks that it does. Eats.frog_fly has type Eats frog fly, which is the goal.
What is the goal now?
There is none. The proof is complete, and Lean says nothing.
How can we be sure nothing is left?
Ask for one more step:
example : Eats frog fly := by
exact Eats.frog_fly
exact Eats.frog_fly
error: No goals to be solved
The second exact has nothing to do.
What is example?
A theorem with no name. Lean checks it and then forgets it. We use example when we only want to see that something checks.
Could we have named the theorem something else?
Yes. The name is ours to choose. Lean checks only the claim and the proof.
What happens if we hand exact a critter?
example : Eats frog fly := by
exact frog
error: Type mismatch
frog
has type
Critter
of sort `Type` but is expected to have type
Eats frog fly
of sort `Prop`
frog is a thing, but a thing of the wrong type. It is a critter, not a proof that the frog eats flies.
What is a sort?
Lean's word for the type of a type. Critter lives in Type, the sort of data. Eats frog fly lives in Prop, the sort of claims.
What is the type of frog_eats_fly?
frog_eats_fly : Eats frog fly
The same as the type of Eats.frog_fly. A theorem is a name for a proof.
Can we use that name in another proof?
Yes, anywhere a proof of Eats frog fly is wanted:
example : Eats frog fly := by
exact frog_eats_flyMust we always write Eats. before the constructor?
No. Lean knows the goal is an Eats claim, so a leading dot looks the name up inside Eats:
example : Eats heron fish := by
exact .heron_fishWhat if we do not remember the name of the constructor at all?
Let Lean find it:
theorem heron_eats_fish :
Eats heron fish := by
constructorWhat is the goal before constructor?
⊢ Eats heron fishWhat does constructor do with it?
It tries the constructors of Eats, one by one, until it finds one whose type matches the goal. Here that is heron_fish. The goal is then met, so no goals are left.
A Claim With No Proof
Can constructor prove Eats fly frog?
example : Eats fly frog := by
constructor
error: Tactic `constructor` failed: no
applicable constructor found
⊢ Eats fly frog
No. Lean also shows the goal it could not meet.
Why did it fail?
None of the eleven constructors has the type Eats fly frog. The closest one, frog_fly, has the critters the other way round.
What if we hand Lean that one anyway?
error: Type mismatch
Eats.frog_fly
has type
Eats frog fly
but is expected to have type
Eats fly frog
The same kind of error as "frog" in place of a critter. A proof of the wrong claim is a thing of the wrong type.
So is Eats fly frog false?
We believe so. But Lean has not said so. A tactic failed, and that is all.
What is the difference?
"Not proved" means no one has found a proof. "False" would need a proof that no proof can exist. Chapter 4 learns to write that.
Did Chapter 1's decide not say "is false"?
It did, about eats fly frog = true. decide could compute the Boolean false and see it is not true. With Eats there is no Boolean to compute, only constructors to look for.
Contents
- 1
Things and Their Types
The pond is back. Algae grow in it. Snails and flies eat the algae. Frogs and fish eat the snails and the flies, a duck eats whatever it can reach, and a heron and an otter stand at the top and eat the frogs and the fish.
- 2
Propositions Are Types
At the end of Chapter 1, Lean agreed that
eats frog flyistrue. It worked the answer out from a table we wrote. If we had made a mistake in the table, it would have agreed with the mistake as calmly. - 3
Rules as Constructors
Is the heron above the algae? Look at the food web and you will say yes at once. The heron eats the frog, the frog eats the fly, and the fly eats the algae. But Lean knows only
Eats, and no constructor ofEatssaysEats heron algae. In Chapter 2, a claim with no constructor behind it could not be proved. - 4
Nothing and Something
In Chapter 2 we tried to prove that the fly eats the frog. No constructor of
Eatsfit, and Lean refused every attempt. But a claim we cannot prove is not yet a claim we have shown to be false. Maybe we did not try hard enough. - 5
Numbers by Hand
The heron stands at the top of the pond. Chapter 3 proved that it is above the algae. It did not say how far above. How many steps is it from the heron down to the algae?
- 6
Equality
Every chapter so far has used
=. Chapter 1 checked thateats frog fly = true, and Chapter 5 proved0 + n = nby induction. We never asked what=itself is, or where its proofs come from. - 7
Lists and Their Laws
The heron eats a frog, the frog eats a fly, and the fly eats algae. Write them in a row and you have a food chain:
[heron, frog, fly, algae]. A row of critters is a list. - 8
Types That Depend on Values
Ask a list of critters for its first critter, and most of the time there is one. But a list can be empty. Ask with
head?, and Lean's lists answer with a shrug that every caller must check, even a caller who knows the list is not empty. - 9
Families of Evidence
In Chapter 1,
by decidechecked thateats frog fly = true. Lean computed the Boolean, foundtrue, and agreed. In Chapter 2 we traded the Boolean for something better: a type,Eats frog fly, whose things are proofs. - 10
A Language That Cannot Go Wrong
The Little Elpier ended with a type checker. It read a small expression such as
add tt (num 1), which asks us to add true to the number one, and saidFailure. The bad expression was written first and turned away after. A type checker is a guard at the door.
The twelve Maxims
Short rules, each earned by the questions of one chapter.
- The First Maxim
Every thing has a type. Ask
#checkbefore you guess. - The Second Maxim
A proposition is a type. A proof is a thing of that type.
- The Third Maxim
A tactic builds a proof. The goal says what is still missing.
- The Fourth Maxim
A rule is a constructor. To use it,
applyit and prove what it asks for. - The Fifth Maxim
To prove "and", prove both. To use "or", handle both.
- The Sixth Maxim
To prove "not P", assume P and reach
False. FromFalse, anything follows. - The Seventh Maxim
To prove "for every", take any one. To prove "there is", show one.
- The Eighth Maxim
A proof about a recursive function follows the function: induction where it recurses.
- The Ninth Maxim
Equal means the same after computing. When computing is not enough, rewrite.
- The Tenth Maxim
A type may mention a value. Then checking the type checks the value.
- The Eleventh Maxim
Evidence is data. What you can decide, let Lean decide.
- The Twelfth Maxim
Make wrong programs impossible to write. Then prove the rest.
Get the code
Every program in the book comes from the companion repository, and every output, error and goal printed in the book is a golden file there that a test checks. It needs only core Lean 4.35.0: no Mathlib and nothing else to download. It runs on macOS, Linux and Windows.
On your machine
Install elan, the Lean installer. The first build fetches the right Lean.
git clone https://github.com/Volland/little-leaner
cd little-leaner
lake build
lake env lean ch01/hello.lean
python3 tests/run_lean.pyIn an editor
Open the folder in Visual Studio Code with the Lean 4 extension. The infoview shows the goal at the cursor, the same goals the book prints.
In the browser
No install: paste lib/Pond.lean and a
chapter file into the Lean web editor. It runs the newest Lean,
so a message may read a little differently.