Now that I'm done writing and defending my thesis (and submitting the associated paper to a journal), I can dive back into Lean full time (for the next few months at least).
I just wrapped up Chapter 5 of MIL, and the final theorem of the chapter, namely the fact that there are infinitely many primes that are equal to \(3\) mod \(4\) was a fun one to formalize: and used an inductive type I had not encountered yet, namely Finset
s.
This chapter also featured the first use of strong induction on Nat
, which modifies the induction tactic to use strong induction.
Working with Finset
also led down to the rabbit hole of constructive vs. classical logic.
To filter a Finset
with some predicate (i.e. a Prop
) P
, the the predicate has to be in the DecidablePred
typeclass.
This would make working with Finset
s of real numbers quite annoying, since inequalities involving real numbers are not decidable (or semi-decidable, whatever that may mean).
Conveniently, one can convince the Lean compiler to work in classical logic, instead of constructive logic, by opening the Classical
namespace.
I suppose this namespace has the law of excluded middle (or some equivalent notion) as an axiom, which makes all Prop
decidable, and makes it possible once again to work with finite sets of reals.
This article by Jeremy Avigad on classical vs constructive logic was a fun read.
Going on a tangent, and thinking about Lean as a programming language, I noticed that some of the more general tactics make it seem like tactics form a monad: namely repeat
, and <;>
which sequences two tactics, as well as some others.
Another analogy is how we start a tactic proof using the by
keyword, very much like the do
notation in Haskell.
It makes sense, since a tactic takes the current proof "state", and applies some operation on it to give a new proof "state".
Chapter 5 of PIL confirms this observation.
I am quite pleased to have identified a familiar monad in a new programming language.
Some more tactics
contradiction
: Closes the current goal if there are contradictory or (decidably) false hypothesis in the environment.by_contra h
: If the goal isp
, it introduces a hypothesish : \not p
, and changes the current goal toFalse
.repeat'
: Similar torepeat
, but runs the tactic on the newly produced goals as well, perhaps? The documentation online is somewhat sparse.t1 <;> t2
: This runs the tactict1
on the main goal, and thent2
on the subsequent goals.push_neg at h
: Ifh
has type\not a \leq b
, it creates a new hypothesish
with typea > b
, and so on.interval_cases x : a
: Creates cases forx
if thex
is a natural or an integer, assuming it can find in the ambient hypotheses upper and lower bounds forx
. If so, it splits into cases for each possible value ofx
. I should try and see if there's a variant of this tactic that splits up cases into sub-intervals.strong_induction_on
: Modifies theinduction'
tactic to use strong induction instead.refine
andrefine'
are generalizations ofexact
andapply
, where you can provide some of the arguments, and it will turn the missing arguments into new goals. More specifically, if you are trying to construct an object of a dependent type, it will let you plug in the pieces you already have.ext
tactic: Theext
tactic applies “extensionality lemmas”. An extensionality lemma says “two things are the same if they are built from the same stuff”. In practice so far, this came up when I was trying to show two setsA
andB
are equal: callingext x
gave me two cases to deal with: it gave me anx \in A
, and the goal was to showx \in B
, and second case was the other way around.tauto
andtauto!
: These were useful in dealing with goals that were tautologically true, at least the set-theoretic ones, and these tactics closed them immediately. I need to figure out what contexts do these tactics work in.decide
: This tactic is useful if the goal is something that Lean has an algorithm to decide: e.g. equality ofNat
, primality ofNat
, etc.let
andset
: This lets us define new variables in a tactic proof. Theset
variant will also rewrite all of the ambient hypotheses to use the new variable if possible.revert
: This isintro
backwards.by_cases h
: This creates two new subgoals, one which assumesh
, and one which assumes\not h
. This requires thath
be decidable (but if we are working with classical logic, this is moot).tidy
: This is a meta tactic that helps you find the right tactics to solve the goal. Not sure how useful this is in the mathlib context.norm-num
: If the goal is an arithmetic expression involving the 4 standard operators, equality, and inequalities, this will reduce the expression to a normal form, which is then decidable.
Reading
- On how schemes were formalized, and the perils of universal properties.
Other small notes
- There is a possibly a typo in Section 5.3 (as of 05/09/2024): the line about
dsimp
is probably stale.