import TutorialWorld.Level1
import TutorialWorld.Level2
import TutorialWorld.Level3
import TutorialWorld.Level4
Natural Numbers Tutorial
This tutorial is based on the Natural Number Game by Kevin Buzzard and Mohammad Pedramfar.
This tutorial provides the same content in a book format that is designed to be read online or run in your local Visual Studio Code with the Lean4 "extension". The extension will install the Lean4 compiler and language service for you so it is easy to setup - see the Quick Start for more information.
To run this tutorial in Visual Studio Code, first
clone the https://github.com/leanprover/lean4-samples
repo. Then you must open the NaturalNumbers
folder in Visual Studio Code using File/Open folder
in order
for it to function correctly.
How to use this sample
When reading this content in a web browser the code samples are annotated with type information and you can see the proof tactic state by hovering your mouse over these little bubbles that look like this:
When viewing this content in Visual Studio Code you will find this tactic state information in the Lean extension "Info View" panel.
This sample is organized into sections called "worlds" and each section has a sequential set of learning levels. The idea is that you should work through the worlds and levels in order. Each level has a description of the goal and a set of tasks to complete.
In this sample you will not use the built in support for natural numbers in Lean because that would
be too easy. You will start from scratch defining your own new type called MyNat
. Your version of
the natural numbers will satisfy something called the principle of mathematical induction, and a
couple of other things too (Peano's axioms). Since you are starting from scratch, you will have to
prove all the basic theorems about your numbers like x + y = y + x
and so on. This is your job.
You're going to prove mathematical theorems using the Lean theorem prover.
You're going to prove these theorems using tactics. The introductory world, Tutorial World, will
take you through some of these tactics. During your proofs, your "goal" (i.e. what you're supposed
to be proving) will be displayed in the Visual Studio Code InfoView with a ⊢
symbol in front of
it. If the InfoView says "Goals accomplished 🎉", you have closed all the goals in the level
and can move on to the next level in the world you're in.
You are now ready to dive into Tutorial World: Level 1.
There is also an "online" version under development that works with Lean 4, see https://github.com/PatrickMassot/NNG4.
If you want to see the original "Lean 3" game version of this content, go to https://github.com/ImperialCollegeLondon/natural_number_game which is also hosted in this online version.
import MyNat.Definition
namespace MyNat
Tutorial world
Level 1: the rfl tactic
Let's learn some tactics! Let's start with the rfl
tactic. rfl
stands for "reflexivity", which is
a fancy way of saying that it will prove any goal of the form A = A
. It doesn't matter how
complicated A
is, all that matters is that the left hand side is exactly equal to the right hand
side (a computer scientist would say "definitionally equal"). I really mean "press the same buttons
on your computer in the same order" equal. For example, x * y + z = x * y + z
can be proved by rfl
,
but x + y = y + x
cannot.
Each level in this world involves proving a theorem or a lemma (a lemma is just a baby theorem). The
goal of the theorem will be a mathematical statement with a ⊢
just before it. We will use tactics
to manipulate and ultimately close (i.e. prove) these goals.
Note that while lean4 does not define the keyword
lemma
it has been added to themathlib4
library so it is coming from theimport Mathlib.Tactic.Basic
that is included inMyNat.Definition
.
Let's see rfl
in action! At the bottom of the text in this box, there's a lemma, which says that
if x
, y
and z
are natural numbers then xy + z = xy + z
. Locate this lemma (if you can't see
the lemma and these instructions at the same time, make this box wider by dragging the sides). Let's
supply the proof. Click on the word sorry and then delete it. When the system finishes being busy,
you'll be able to see your goal in the Visual Studio Code InfoView.
Remember that the goal is the thing with the unicode symbol ⊢
just before it. The goal in this
case is x * y + z = x * y + z
, where x
, y
and z
are some of your very own natural numbers.
That's a pretty easy goal to prove -- you can just prove it with the rfl
tactic. Where it used to
say sorry, write rfl
.
Lemma
For all natural numbers x
, y
and z
, we have xy + z = xy + z
.
lemmaexample1 (example1: ∀ (x y z : MyNat), x * y + z = x * y + zxx: MyNatyy: MyNatz :z: MyNatMyNat) :MyNat: Typex *x: MyNaty +y: MyNatz =z: MyNatx *x: MyNaty +y: MyNatz :=z: MyNatx, y, z: MyNatx * y + z = x * y + zGoals accomplished! 🐙
If all goes well Lean will print Goals accomplished 🎉
in the InfoView.
You just did the first level of the tutorial!
For each level, the idea is to get Lean into this state: with the InfoView saying "Goals accomplished 🎉
"
when the cursor is placed at the end of the line that completes the proof.
If you want to be reminded about the rfl
tactic, you can hover the mouse over the rfl
keyword and
a tooltip will appear with information about this tactic. You can also press F12 to jump to the
definition of that tactic were there will be lots more handy information.
We have also included a Tactics Section that lists all the tactics we use in this tutorial.
Now click on "next level" in the top right of your browser to go onto the second level of tutorial world, where we'll learn about the rw tactic.
Now you are ready for Level2.lean.
import MyNat.Definition
namespace MyNat
Tutorial world
Level 2: The rewrite
tactic
The rewrite
tactic is the way to "substitute in" the value of a variable. In general, if you have a
hypothesis of the form A = B
, and your goal mentions the left hand side A
somewhere, then the
rewrite tactic will replace the A
in your goal with a B
. Below is a theorem which cannot be proved
using rfl
-- you need a rewrite
first.
Take a look in the InfoView at what you have. The variables
x
and y
are natural numbers, and there is a proof h
that y = x + 7
.
Your goal then is to prove that 2y = 2(x + 7)
. This goal is obvious -- you just substitute in
y = x + 7
and you're done. In Lean, you do this substitution using the rewrite
tactic.
Lemma
If x
and y
are natural numbers, and y = x + 7
, then 2y = 2(x + 7)
.
lemmaexample2 (example2: ∀ (x y : MyNat), y = x + 7 → 2 * y = 2 * (x + 7)xx: MyNaty :y: MyNatMyNat) (MyNat: Typeh :h: y = x + 7y =y: MyNatx +x: MyNat7) :7: MyNat2 *2: MyNaty =y: MyNat2 * (2: MyNatx +x: MyNat7) :=7: MyNatx, y: MyNat
h: y = x + 72 * y = 2 * (x + 7)x, y: MyNat
h: y = x + 72 * y = 2 * (x + 7)x, y: MyNat
h: y = x + 72 * (x + 7) = 2 * (x + 7)x, y: MyNat
h: y = x + 72 * (x + 7) = 2 * (x + 7)Goals accomplished! 🐙
Did you see what happened to the goal? (Put your cursor at the end of the rewrite
line).
The goal doesn't close, but it changes from ⊢ 2 * y = 2 * (x + 7)
to ⊢ 2 * (x + 7) = 2 * (x + 7)
.
And since these are now identical you can just close this goal with rfl
.
You should now see "Goals accomplished 🎉" (with cursor at the end of the rfl
line).
The square brackets here is a List
object
because rewrite
can rewrite using multiple hypotheses in sequence.
If you are reading this book online you can move the mouse over each bubble that is added to the end of each line (that look like this: ) to see what the tactic state is at that point in the proof.
The other way you know the goal is complete is to look a the Visual Studio Code Problems list window, if there are no error saying "unsolved goals" then you are done.
The documentation for rewrite
will appear when you hover the mouse over it. We have also included
a Tactics Section that lists all the tactics we use in this tutorial.
Now, Lean has another similar tactic named rw
which does both the rewrite
and the rfl
. Try changing to rw
above and you will see the rfl
is
no longer needed.
Details
Now you are ready for Level3.lean.
import MyNat.Definition
namespace MyNat
open MyNat
Tutorial world
Level 3: Peano's axioms.
The import above gives us the type MyNat
of natural numbers. But it also gives us some other things,
which we'll take a look at now:
- a term
(0 : MyNat)
, interpreted as theMyNat.zero
. - a function
succ : MyNat → MyNat
, withsucc n
interpreted as "the number after n", or the successor ofn
. - The principle of mathematical induction.
These axioms are essentially the axioms isolated by Peano which uniquely characterize the natural
numbers (you also need recursion, but you can ignore it for now). The first axiom says that 0 is a
natural number. The second says that there is a succ
function which eats a number and spits out the
number after it, so succ 0 = 1
, succ 1 = 2
, and so on.
Peano's last axiom is the principle of mathematical induction. This is a deeper fact. It says that
if you have infinitely many true/false statements P(0)
, P(1)
, P(2)
, and so on, and if
P(0)
is true, and if for every natural number d
you know that P(d)
implies P(succ d)
, then
P(n)
must be true for every natural number n
.
It's like saying that if you have a long line of dominoes, and if you knock the first one down, and
if you know that if a domino falls down then the one after it will fall down too, then you can
deduce that all the dominos will fall down. You can also think of it as saying that every natural
number can be built by starting at 0 and then applying succ
a finite number of times.
Peano's insights were firstly that these axioms completely characterise the natural numbers, and secondly that these axioms alone can be used to build a whole bunch of other structure on the natural numbers, for example addition, multiplication and so on.
This world is all about seeing how far these axioms of Peano can take us.
Let's practice your use of the rewrite
tactic in the following example. The hypothesis h
is a proof that
succ a = b
and you want to prove that succ (succ a) = succ b
. In words, you're going to prove that if
b
is the number after a
then succ b
is the number after succ a
. Now here's a tricky question. If
your goal is ⊢ succ (succ a) = succ b
, and your hypothesis is h : succ a = b
, then what will the goal
change to when we type rewrite [h]
?
Lemma
If succ a = b
, then succ (succ a) = succ b
.
lemmaexample3 (example3: ∀ (a b : MyNat), succ a = b → succ (succ a) = succ baa: MyNatb :b: MyNatMyNat) (MyNat: Typeh : (h: succ a = bsuccsucc: MyNat → MyNata) =a: MyNatb) :b: MyNatsucc (succ: MyNat → MyNatsuccsucc: MyNat → MyNata) =a: MyNatsuccsucc: MyNat → MyNatb :=b: MyNata, b: MyNat
h: succ a = bsucc (succ a) = succ ba, b: MyNat
h: succ a = bsucc (succ a) = succ ba, b: MyNat
h: succ a = bsucc b = succ ba, b: MyNat
h: succ a = bsucc b = succ bGoals accomplished! 🐙
Remember that rewrite [h]
will look for the left hand side of h
in the goal, and will replace it with
the right hand side. Try and figure out how the goal will change, and then try it.
The answer: Lean changed succ a
into b
, so the goal became succ b = succ b
. That goal is of
the form X = X
, so you can complete prove the proof using rfl
which rw
does for you.
Important note : the tactic rewrite
expects a proof afterwards (e.g. rewrite [h1]
).
But rfl
is just rfl
.
You may be wondering whether you could have just substituted in the definition of b
and proved the
goal that way. To do that, you would want to replace the right hand side of h
with the left hand
side. You do this in Lean by writing rw [← h]
. You get the left-arrow by typing \l
and then a space;
note that this is a small letter L
, not a number 1
. You can just edit your proof and try it.
You may also be wondering why we keep writing succ b
instead of b + 1
. This is because we haven't
defined addition yet! On the next level, the final level of Tutorial World, we will introduce
addition, and then you'll be ready to enter Addition World.
Now you are ready for Level4.lean.
import MyNat.Addition
namespace MyNat
open MyNat
Tutorial world
Level 4: addition
We have a new import -- the definition of addition.
Peano defined addition a + b
by induction on b
, or, more precisely, by recursion on b
. He first
explained how to add 0 to a number: this is the base case.
add_zero (a : MyNat) : a + 0 = a
We will call this theorem add_zero
. More precisely, add_zero
is the name of the proof of the
theorem. Note the name of this proof. Mathematicians sometimes call it "Lemma 2.1" or "Hypothesis
P6" or something. But computer scientists call it add_zero
because it tells you what the answer to
"x add zero" is. It's a much better name than "Lemma 2.1". Even better, you can use the rewrite
tactic
with add_zero
. If you ever see x + 0
in your goal, rewrite [add_zero]
should simplify it to x
. This is
because add_zero
is a proof that x + 0 = x
(more precisely, add_zero x
is a proof that x + 0 = x
but
Lean can figure out the x
from the context).
Now here's the inductive step. If you know how to add d
to a
, then Peano tells you how to add
succ d
to a
. It looks like this:
add_succ (a d : MyNat) : a + (succ d) = succ (a + d)
What's going on here is that we assume a + d
is already defined, and we define a + (succ d)
to
be the number after it. Note the name of this proof too -- add_succ
tells you how to add a successor
to something. If you ever see ... + succ ...
in your goal, you should be able to use rewrite [add_succ]
, to
make progress.
Lemma`
For all natural numbers a
, we have a + (succ 0
) = succ a`.
lemmaadd_succ_zero (add_succ_zero: ∀ (a : MyNat), a + succ 0 = succ aa :a: MyNatMyNat) :MyNat: Typea + (a: MyNatsuccsucc: MyNat → MyNat0) =0: MyNatsuccsucc: MyNat → MyNata :=a: MyNata: MyNata + succ 0 = succ aa: MyNata + succ 0 = succ aa: MyNatsucc (a + 0) = succ aa: MyNatsucc (a + 0) = succ aa: MyNatsucc (a + 0) = succ aa: MyNatsucc a = succ aa: MyNatsucc a = succ aGoals accomplished! 🐙
Do you see that the goal at the end of the first rewrite
now mentions ... + 0 ...
? So this
matches the add_zero
theorem so you can now add the rewrite [add_zero]
.
After the rfl
the proof is now complete: "Goals accomplished 🎉".
Note that because rewrite
takes a list, you can also write the above two lines in one
using rewrite [add_succ, add_zero]
.
And remember rw
also does rfl
, you can replace the whole proof with rw [add_succ, add_zero]
.
Examining proofs.
You might want to review this proof now; at three lines long it is your current record. Don't worry there are much longer proofs, in fact, the Liquid Tensor Experiment contains 90,000 lines of Lean proofs! For this reason Lean is a real programming language with support for abstraction and extension so that you can get as much reusability as possible in your Lean code.
The easiest way to see how the proof goal state progresses is to place your cursor at the beginning of each line using the Up/Down arrow key to move down the proof and see the effect of the previous line on the goal shown in the InfoView.
Next
You have finished tutorial world! When you're happy, please move onto Addition World, and learn about proof by induction.
Troubleshooting
Question: why has the InfoView gone blank?
Answer: try placing the cursor at different places in the file, the InfoView shows the context at the cursor location. If the InfoView is not updating at all no matter what you do then there might be a problem with your VS Code setup. See the Quick Start for more information.
import AdditionWorld.Level1
import AdditionWorld.Level2
import AdditionWorld.Level3
import AdditionWorld.Level4
import AdditionWorld.Level5
import AdditionWorld.Level6
Addition World.
Welcome to Addition World. If you've done all four levels in Tutorial
World and know about rewrite
, rw
and rfl
, then you're in the
right place. We'll use rw
from her on out just for convenience. Here's a reminder of the things
you're now equipped with which we'll need in this world.
Data:
- a type called
MyNat
- a term
(0 : MyNat)
, interpreted as the number zero. - a function
succ : MyNat → MyNat
, withsucc n
interpreted as "the number aftern
". - Usual numerical notation 0,1,2 etc (although 2 onwards will be of no use to us until much later ;-) ).
- Addition (with notation
a + b
).
Theorems:
add_zero (a : MyNat) : a + 0 = a
. Use withrw [add_zero]
.add_succ (a b : MyNat) : a + succ b = succ (a + b)
. Use withrw [add_succ]
.- The principle of mathematical induction. Use with
induction
(see below)
Tactics:
rfl
: proves goals of the formX = X
rw [h]
: if h is a proof ofA = B
, changes all A's in the goal to B's.induction n with ...
: we're going to learn this right now.
Important thing
This is a really good time to check you can get "mouse hover help on anything in the Lean program. If you hover over 'rfl' you get information on that tactic. You can also press F12 to jump right into the definition of all the helpers functions we use here.
On to Addition World Level 1.
import MyNat.Addition -- imports addition.
namespace MyNat
open MyNat
Addition World.
Level 1: the induction
tactic.
OK so let's see induction in action. We're going to prove
zero_add (n : MyNat) : 0 + n = n
.
That is: for all natural numbers n
, 0+n=n
. Wait - what is going on here? Didn't you already prove
that adding zero to n
gave us n
? No you didn't! You proved n + 0 = n
, and that proof was called
add_zero
. We're now trying to establish zero_add
, the proof that 0 + n = n
. But aren't these
two theorems the same? No they're not! It is true that x + y = y + x
, but you haven't proved it
yet, and in fact you will need both add_zero
and zero_add
in order to prove this. In fact
x + y = y + x
is the boss level for addition world, and induction
is the only other tactic you'll
need to beat it.
Now add_zero
is one of Peano's axioms, so you don't need to prove it, you already have it (indeed,
if you've used Goto Definition (F12) on this theorem you can even see it). To prove 0 + n = n
we
need to use induction on n
. While we're here, note that zero_add
is about zero add something,
and add_zero
is about something add zero. The names of the proofs tell you what the theorems are.
Lemma
For all natural numbers n
, we have 0 + n = n.
lemmazero_add (zero_add: ∀ (n : MyNat), 0 + n = nn :n: MyNatMyNat) :MyNat: Type0 +0: MyNatn =n: MyNatn :=n: MyNatn: MyNat0 + n = nn: MyNat0 + n = n
zero0 + zero = zeroGoals accomplished! 🐙n: MyNat
ih: 0 + n = n
succ0 + succ n = succ nn: MyNat
ih: 0 + n = n
succ0 + succ n = succ nn: MyNat
ih: 0 + n = n
succsucc (0 + n) = succ nn: MyNat
ih: 0 + n = n
succsucc (0 + n) = succ nn: MyNat
ih: 0 + n = n
succsucc (0 + n) = succ nn: MyNat
ih: 0 + n = n
succsucc n = succ nGoals accomplished! 🐙
Notice that the induction tactic has created two sub-goals which you can match using vertical bar pattern patching.
The induction tactic has generated for us a base case with n = zero
(the goal at the top)
and an inductive step (the goal underneath). The golden rule: Tactics operate on the first goal
- the goal at the top. So let's just worry about that top goal now, the base case.
If you place the cursor right after the
=>
symbol you will see the goal listed in the InfoView as⊢ 0 + zero = zero
.
Remember that add_zero
(the proof we have already) is the proof of x + 0 = x
(for any x
) so you can try rw [add_zero]
here but what do you think the goal will change to?
Remember to just keep focussing on the top goal, ignore
the other one for now, it's not changing and in fact, the InfoView tells you why:
tactic 'rewrite' failed, did not find instance of the pattern in the target expression
But as you can see rfl
can solve the first case. You should now see Goals accomplished 🎉
when
your cursor is placed on the right of the rfl
which means you have solved this base case sub-goal,
and you are ready to tackle the next sub-goal -- the inductive step. Take a look at the text below
the lemma to see an explanation of this goal.
In the successor case the InfoView tactic state should look something like this:
case succ
n: MyNat
ih: 0 + n = n
⊢ 0 + succ n = succ n
Important: make sure that you only have one goal at this point. You should have proved 0 + 0 = 0
by now. Tactics only operate on the top goal.
The first line just reminds you you're doing the inductive step. You have a fixed natural number n
,
and the inductive hypothesis ih : 0 + n = n
which means this hypothesis proves 0 + n = n
. Your
goal is to prove 0 + succ n = succ n
. In other words, we're showing that if the lemma is true for
n
, then it's also true for n + 1
. That's the inductive step that you might be familiar with in
proof by induction. Once we've proved this inductive step, you will have proved zero_add
by the
principle of mathematical induction.
To prove your goal, you need to use add_succ
which you proved in
Tutorial World Level 4. Note that add_succ 0 d
is the result that 0 + succ d = succ (0 + d)
, so the first thing
you need to do is to replace the left hand side 0 + succ d
of your
goal with the right hand side. You do this with the rw
command: rw [add_succ]
(or even rw [add_succ 0 n]
if you want to give Lean all the inputs instead of making it
figure them out itself). Notice goal changes to ⊢ succ (0 + n) = succ n
.
Now remember the inductive hypothesis ih : 0 + d = d
. This 0 + d
matches
the (0 + n)
in the goal, so you can write that using rw [ih]
.
The goal will now change to
⊢ succ d = succ d
and the rw
tactic will automatically finish our proof using the rfl
tactic.
After you apply it, Lean will inform you that there are no goals left. You are done!
Remember that you can write rw [add_succ, ih]
also, but notice that rewriting is
order dependent and that rw [ih, add_succ]
does not work.
Now venture off on your own
Those three tactics --
induction n with ...
rw [h]
rfl
will get you quite a long way through this tutorial. Using only these tactics
do all of Addition World,
all of Multiplication World including the boss level a * b = b * a
,
and even all of Power World including the fiendish final boss. This route will
give you a good grounding in these three basic tactics; after that, if you
are still interested, there are other worlds to master, where you can learn
more tactics.
But we're getting ahead of ourselves, you still have to read the rest of Addition World.
We're going to stop explaining stuff carefully now. If you get stuck or want
to know more about Lean (e.g. how to do much harder maths in Lean),
ask in #new members
at the Lean chat.
(login required, real name preferred, github account id is handy).
Kevin or Mohammad or one of the other people there might be able to help.
On to level 2.
import MyNat.Addition -- imports addition.
namespace MyNat
open MyNat
Addition world
Level 2: add_assoc
-- associativity of addition.
It's well-known that (1 + 2) + 3 = 1 + (2 + 3) -- if you have three numbers
to add up, it doesn't matter which of the additions you do first. This fact
is called associativity of addition by mathematicians, and it is not
obvious. For example, subtraction really is not associative: (6 - 2) - 1
is really not equal to 6 - (2 - 1)
. We are going to have to prove
that addition, as defined the way we've defined it, is associative.
To prove associativity of addition it is handy to recall that addition was defined
by recursion on the right-most variable. This means you can use induction on the right-most
variable (try other variables at your peril!). Note that when Lean writes a + b + c
,
it means (a + b) + c
. If it wants to talk about a + (b + c)
it will put the brackets
in explicitly.
Reminder: you are done when you see "Goals accomplished 🎉" in the InfoView, and no errors in the VS Code Problems list.
Lemma
On the set of natural numbers, addition is associative.
In other words, for all natural numbers a, b
and c
, we have
(a + b) + c = a + (b + c).
lemmaadd_assoc (add_assoc: ∀ (a b c : MyNat), a + b + c = a + (b + c)aa: MyNatbb: MyNatc :c: MyNatMyNat) : (MyNat: Typea +a: MyNatb) +b: MyNatc =c: MyNata + (a: MyNatb +b: MyNatc) :=c: MyNata, b, c: MyNata + b + c = a + (b + c)a, b, c: MyNata + b + c = a + (b + c)a, b: MyNat
zeroa + b + zero = a + (b + zero)a, b: MyNat
zeroa + b + zero = a + (b + zero)a, b: MyNat
zeroa + b + 0 = a + (b + 0)a, b: MyNat
zeroa + b + 0 = a + (b + 0)a, b: MyNat
zeroa + b + 0 = a + (b + 0)a, b: MyNat
zeroa + b = a + (b + 0)a, b: MyNat
zeroa + b = a + (b + 0)a, b: MyNat
zeroa + b = a + (b + 0)a, b: MyNat
zeroa + b = a + bGoals accomplished! 🐙a, b, c: MyNat
ih: a + b + c = a + (b + c)
succa + b + succ c = a + (b + succ c)a, b, c: MyNat
ih: a + b + c = a + (b + c)
succa + b + succ c = a + (b + succ c)a, b, c: MyNat
ih: a + b + c = a + (b + c)
succsucc (a + b + c) = a + (b + succ c)a, b, c: MyNat
ih: a + b + c = a + (b + c)
succsucc (a + b + c) = a + (b + succ c)a, b, c: MyNat
ih: a + b + c = a + (b + c)
succsucc (a + b + c) = a + (b + succ c)a, b, c: MyNat
ih: a + b + c = a + (b + c)
succsucc (a + b + c) = a + succ (b + c)a, b, c: MyNat
ih: a + b + c = a + (b + c)
succsucc (a + b + c) = a + succ (b + c)a, b, c: MyNat
ih: a + b + c = a + (b + c)
succsucc (a + b + c) = a + succ (b + c)a, b, c: MyNat
ih: a + b + c = a + (b + c)
succsucc (a + b + c) = succ (a + (b + c))a, b, c: MyNat
ih: a + b + c = a + (b + c)
succsucc (a + b + c) = succ (a + (b + c))a, b, c: MyNat
ih: a + b + c = a + (b + c)
succsucc (a + b + c) = succ (a + (b + c))a, b, c: MyNat
ih: a + b + c = a + (b + c)
succsucc (a + (b + c)) = succ (a + (b + c))Goals accomplished! 🐙
On to Level 3.
import MyNat.Addition -- imports addition.
namespace MyNat
open MyNat
Addition World
Level 3: succ_add
Oh no! On the way to add_comm
, a wild succ_add
appears. succ_add
is the proof that (succ a) + b = succ (a + b)
for a
and b
in your
natural number type. You need to prove this now, because you will need
to use this result in our proof that a + b = b + a
in the next level.
Think about why computer scientists called this result succ_add
.
There is a logic to all the names.
Note that if you want to be more precise about exactly where you want
to rewrite something like add_succ
(the proof you already have),
you can do things like rw [add_succ (succ a)]
or
[rw add_succ (succ a) d]
, telling Lean explicitly what to use for
the input variables for the function add_succ
. Indeed, add_succ
is a function -- it takes as input two variables a
and b
and outputs a proof
that a + succ b = succ (a + b)
. The tactic rw [add_succ]
just says to Lean "guess
what the variables are".
Lemma
For all natural numbers a, b
, we have (succ a) + b = succ (a + b).
lemmasucc_add (succ_add: ∀ (a b : MyNat), succ a + b = succ (a + b)aa: MyNatb :b: MyNatMyNat) :MyNat: Typesuccsucc: MyNat → MyNata +a: MyNatb =b: MyNatsucc (succ: MyNat → MyNata +a: MyNatb) :=b: MyNata, b: MyNatsucc a + b = succ (a + b)a, b: MyNatsucc a + b = succ (a + b)a: MyNat
zerosucc a + zero = succ (a + zero)Goals accomplished! 🐙a, b: MyNat
ih: succ a + b = succ (a + b)
succsucc a + succ b = succ (a + succ b)a, b: MyNat
ih: succ a + b = succ (a + b)
succsucc a + succ b = succ (a + succ b)a, b: MyNat
ih: succ a + b = succ (a + b)
succsucc (succ a + b) = succ (a + succ b)a, b: MyNat
ih: succ a + b = succ (a + b)
succsucc (succ a + b) = succ (a + succ b)a, b: MyNat
ih: succ a + b = succ (a + b)
succsucc (succ a + b) = succ (a + succ b)a, b: MyNat
ih: succ a + b = succ (a + b)
succsucc (succ (a + b)) = succ (a + succ b)a, b: MyNat
ih: succ a + b = succ (a + b)
succsucc (succ (a + b)) = succ (a + succ b)a, b: MyNat
ih: succ a + b = succ (a + b)
succsucc (succ (a + b)) = succ (a + succ b)a, b: MyNat
ih: succ a + b = succ (a + b)
succsucc (succ (a + b)) = succ (succ (a + b))Goals accomplished! 🐙
On to Level 4.
import MyNat.Addition -- imports addition.
import AdditionWorld.Level1 -- zero_add
import AdditionWorld.Level3 -- succ_add
namespace MyNat
open MyNat
Addition World
Level 4: add_comm
In this level, you'll prove that addition is commutative.
Lemma
On the set of natural numbers, addition is commutative.
In other words, for all natural numbers a
and b
, we have a + b = b + a.
lemmaadd_comm (add_comm: ∀ (a b : MyNat), a + b = b + aaa: MyNatb :b: MyNatMyNat) :MyNat: Typea +a: MyNatb =b: MyNatb +b: MyNata :=a: MyNata, b: MyNata + b = b + aa, b: MyNata + b = b + aa: MyNat
zeroa + zero = zero + aa: MyNat
zeroa + zero = zero + aa: MyNat
zeroa + 0 = 0 + aa: MyNat
zeroa + 0 = 0 + aa: MyNat
zeroa + 0 = 0 + aa: MyNat
zeroa = 0 + aa: MyNat
zeroa = 0 + aa: MyNat
zeroa = 0 + aa: MyNat
zeroa = aGoals accomplished! 🐙a, b: MyNat
ih: a + b = b + a
succa + succ b = succ b + aa, b: MyNat
ih: a + b = b + a
succa + succ b = succ b + aa, b: MyNat
ih: a + b = b + a
succsucc (a + b) = succ b + aa, b: MyNat
ih: a + b = b + a
succsucc (a + b) = succ b + aa, b: MyNat
ih: a + b = b + a
succsucc (a + b) = succ b + aa, b: MyNat
ih: a + b = b + a
succsucc (b + a) = succ b + aa, b: MyNat
ih: a + b = b + a
succsucc (b + a) = succ b + aa, b: MyNat
ih: a + b = b + a
succsucc (b + a) = succ b + aa, b: MyNat
ih: a + b = b + a
succsucc (b + a) = succ (b + a)Goals accomplished! 🐙
If you are keeping up so far then nice! You're nearly ready to make a choice: Multiplication World or Function World. But there are just a couple more useful lemmas in Addition World which you should prove first.
Press on to level 5.
import MyNat.Addition
namespace MyNat
open MyNat
Addition World
Level 5: succ_eq_add_one
axiom one_eq_succ_zero: 1 = succ 0
one_eq_succ_zero : (1: MyNat
1 : MyNat: Type
MyNat) = MyNat.succ: MyNat → MyNat
MyNat.succ 0: MyNat
0
We've just added one_eq_succ_zero
(a statement that 1 = succ 0
).
This is not a proof it is an axiom. In Lean an axiom
tells Lean
don't both looking for a proof for this fact, just trust me, it's true.
So you must be very careful in how you use axiom
because it can
lead to inconsistencies in subsequent proofs if your axiom contains
an error.
Levels 5 and 6 are the two last levels in Addition World.
Level 5 involves the number 1
. When you see a 1
in your goal,
you can write rw [one_eq_succ_zero]
to get back
to something which only mentions 0
. This is a good move because 0
is easier for us to
manipulate than 1
right now, because we have
some theorems about 0
(zero_add
, add_zero
), but, other than 1 = succ 0
,
no theorems at all which mention 1
. Let's prove one now.
Theorem
For any natural number n
, we have succ n = n + 1.
theoremsucc_eq_add_one (succ_eq_add_one: ∀ (n : MyNat), succ n = n + 1n :n: MyNatMyNat) :MyNat: Typesuccsucc: MyNat → MyNatn =n: MyNatn +n: MyNat1 :=1: MyNatn: MyNatsucc n = n + 1n: MyNatsucc n = n + 1n: MyNatsucc n = n + succ 0n: MyNatsucc n = n + succ 0n: MyNatsucc n = n + succ 0n: MyNatsucc n = succ (n + 0)n: MyNatsucc n = succ (n + 0)Goals accomplished! 🐙
Note that lemma
and theorem
are the same thing and can be used
interchangeably.
Hint: if you use proof by induction, but then find you don't need the hypothesis in the inductive step, then you probably didn't need proof by induction.
Press on to level 6.
import MyNat.Addition
import AdditionWorld.Level2 -- add_assoc
import AdditionWorld.Level4 -- add_comm
import AdditionWorld.Level5 -- succ_eq_add_one
namespace MyNat
open MyNat
Addition World
Level 6: add_right_comm
Lean sometimes writes a + b + c
. What does it mean? The convention is
that if there are no brackets displayed in an addition formula, the brackets
are around the left most +
(Lean's addition is "left associative").
So the goal in this level is (a + b) + c = (a + c) + b
. This isn't
quite add_assoc
or add_comm
, it's something you'll have to prove
by putting these two theorems together.
If you hadn't picked up on this already, rw [add_assoc]
will
change (x + y) + z
to x + (y + z)
, but to change it back
you will need rw [← add_assoc]
. Get the left arrow by typing \l
then the space bar (note that this is L for left, not a number 1).
Similarly, if h : a = b
then rw [h]
will change a
's to b
's
and rw [← h]
will change b
's to a
's.
Also, you can be (and will need to be, in this level) more precise
about where to rewrite theorems. rw [add_comm]
will just find the
first ? + ?
it sees and swap it around. You can target more specific
additions like this: rw [add_comm a]
will swap around
additions of the form a + ?
, and rw [add_comm a b]
will only
swap additions of the form a + b
.
Where next?
There are thirteen more levels about addition after this one, but before you can attempt them you need to learn some more tactics. So after this level you have a choice -- either move on to Multiplication World (which you can solve with the tactics you know) or try Function World (and learn some new ones). Other things, perhaps of interest to some players, are mentioned below the lemma.
Lemma
For all natural numbers a, b
and c
, we have a + b + c = a + c + b.
lemmaadd_right_comm (add_right_comm: ∀ (a b c : MyNat), a + b + c = a + c + baa: MyNatbb: MyNatc :c: MyNatMyNat) :MyNat: Typea +a: MyNatb +b: MyNatc =c: MyNata +a: MyNatc +c: MyNatb :=b: MyNata, b, c: MyNata + b + c = a + c + ba, b, c: MyNata + b + c = a + c + ba, b, c: MyNata + (b + c) = a + c + ba, b, c: MyNata + (b + c) = a + c + ba, b, c: MyNata + (b + c) = a + c + ba, b, c: MyNata + (c + b) = a + c + ba, b, c: MyNata + (c + b) = a + c + ba, b, c: MyNata + (c + b) = a + c + ba, b, c: MyNata + c + b = a + c + bGoals accomplished! 🐙
If you have got this far, then you have become very good at manipulating equalities in Lean. You can
also now connect four handy type class instances
to your MyNat
type as follows:
-- instance : AddSemigroup MyNat where
-- add_assoc := add_assoc
-- instance : AddCommSemigroup MyNat where
-- add_comm := add_comm
-- instance : AddMonoid MyNat where
-- nsmul := λ x y => (myNatFromNat x) * y
-- nsmul_zero' := MyNat.zero_mul
-- nsmul_succ' n x := by
-- show ofNat (MyNat.succ n) * x = x + MyNat n * x
-- rw [MyNat.ofNat_succ, MyNat.add_mul, MyNat.add_comm, MyNat.one_mul]
-- instance : AddCommMonoid MyNat where
-- zero_add := zero_add
-- add_zero := add_zero
-- add_comm := add_comm
-- nsmul_zero' := ...
-- nsmul_succ' := ...
-- BUGBUG: really? These last two require theorems about multiplication?
-- like add_mul and zero_mul, which is dependent on mul_comm...?
In Multiplication World you will be able to collect such
advanced collectibles as MyNat.comm_semiring
and
MyNat.distrib
, and then move on to Power World and
the famous collectible at the end of it.
One last thing -- didn't you think that solving this level
add_right_comm
was boring? Check out this AI that can do it for us.
The simp
AI (it's just an advanced tactic really), and can nail some really
tedious-for-a-human-to-solve goals. For example, check out this one-line proof.
First you need to teach simp
about the building blocks you have created so far:
lemmaadd_left_comm (add_left_comm: ∀ (a b c : MyNat), a + (b + c) = b + (a + c)aa: MyNatbb: MyNatc :c: MyNatMyNat) :MyNat: Typea + (a: MyNatb +b: MyNatc) =c: MyNatb + (b: MyNata +a: MyNatc) :=c: MyNata, b, c: MyNata + (b + c) = b + (a + c)a, b, c: MyNata + (b + c) = b + (a + c)a, b, c: MyNata + b + c = b + (a + c)a, b, c: MyNata + b + c = b + (a + c)a, b, c: MyNata + b + c = b + (a + c)a, b, c: MyNatb + a + c = b + (a + c)a, b, c: MyNatb + a + c = b + (a + c)a, b, c: MyNatb + a + c = b + (a + c)a, b, c: MyNatb + (a + c) = b + (a + c)attribute [simp]Goals accomplished! 🐙add_assocadd_assoc: ∀ (a b c : MyNat), a + b + c = a + (b + c)add_commadd_comm: ∀ (a b : MyNat), a + b = b + aadd_left_commadd_left_comm: ∀ (a b c : MyNat), a + (b + c) = b + (a + c)example (example: ∀ (a b c d e : MyNat), a + b + c + d + e = c + (b + e + a) + daa: MyNatbb: MyNatcc: MyNatdd: MyNate :e: MyNatMyNat) : (((MyNat: Typea+a: MyNatb)+b: MyNatc)+c: MyNatd)+d: MyNate=(e: MyNatc+((c: MyNatb+b: MyNate)+e: MyNata))+a: MyNatd :=d: MyNata, b, c, d, e: MyNata + b + c + d + e = c + (b + e + a) + dGoals accomplished! 🐙
Imagine having to do that one by hand! The AI closes the goal because it knows how to use associativity and commutativity sensibly in a commutative monoid.
For more info see the simp tactic.
You are now done with addition world. You can now decide whether to press on with
Multiplication World and Power World
(which can be solved with rw
, rfl
and induction
), or to go on to
Function World where you can learn the tactics needed to prove goals of the form P → Q
, thus
enabling you to solve more advanced addition world levels such as a + t = b + t → a = b
. Note that
Function World is more challenging mathematically;
but if you can do Addition World you can surely
do Multiplication World and Power World.
import MultiplicationWorld.Level1
import MultiplicationWorld.Level2
import MultiplicationWorld.Level3
import MultiplicationWorld.Level4
import MultiplicationWorld.Level5
import MultiplicationWorld.Level6
import MultiplicationWorld.Level7
import MultiplicationWorld.Level8
import MultiplicationWorld.Level9
Multiplication World
A new import import MyNat.Multiplication
!
This import gives you the definition of multiplication on your natural numbers. It is defined by recursion, just like addition. Here are the two new axioms:
mul_zero (a : MyNat) : a * 0 = 0
mul_succ (a b : MyNat) : a * succ b = a * b + a
In words, we define multiplication by "induction on the second variable", with a * 0
defined to be
0
and, if we know a * b
, then a
times the number after b
is defined to be a * b + a
.
You can keep all the theorems you proved about addition, but for multiplication, those two results above are what you've got right now.
What's going on in multiplication world? Like addition, we need to go for the proofs that
multiplication is commutative and associative, but as well as that we will need to prove facts about
the relationship between multiplication and addition, for example a * (b + c) = a * b + a * c
, so
now there is a lot more to do. Good luck!
You have been given mul_zero
, and the first thing to prove is zero_mul
.
Like zero_add
, we of course prove it by induction.
Let's get started with Multiplication Level 1.
import MyNat.Addition
import MyNat.Multiplication
namespace MyNat
open MyNat
Level 1: zero_mul
Lemma
For all natural numbers m
, we have 0 * m = 0.
lemmazero_mul (zero_mul: ∀ (m : MyNat), 0 * m = 0m :m: MyNatMyNat) :MyNat: Type0 *0: MyNatm =m: MyNat0 :=0: MyNatm: MyNat0 * m = 0m: MyNat0 * m = 0
zero0 * zero = 0
zero0 * zero = 0
zero0 * 0 = 0
zero0 * 0 = 0
zero0 * 0 = 0
zero0 = 0Goals accomplished! 🐙m: MyNat
ih: 0 * m = 0
succ0 * succ m = 0m: MyNat
ih: 0 * m = 0
succ0 * succ m = 0m: MyNat
ih: 0 * m = 0
succ0 * m + 0 = 0m: MyNat
ih: 0 * m = 0
succ0 * m + 0 = 0m: MyNat
ih: 0 * m = 0
succ0 * m + 0 = 0m: MyNat
ih: 0 * m = 0
succ0 + 0 = 0m: MyNat
ih: 0 * m = 0
succ0 + 0 = 0m: MyNat
ih: 0 * m = 0
succ0 + 0 = 0m: MyNat
ih: 0 * m = 0
succ0 = 0Goals accomplished! 🐙
Next up is Multiplication Level 2.
import MyNat.Addition
import MyNat.Multiplication
import AdditionWorld.Level1 -- zero_add
import AdditionWorld.Level5 -- one_eq_succ_zero
namespace MyNat
open MyNat
Multiplication World
Level 2: mul_one
In this level we'll need to use
one_eq_succ_zero : 1 = succ 0
which was mentioned back in Addition World Level 5 and which will
be a useful thing to rewrite right now, as we begin to prove a couple of lemmas about how 1
behaves with respect to multiplication.
Lemma
For any natural number m
, we have m * 1 = m.
lemmamul_one (mul_one: ∀ (m : MyNat), m * 1 = mm :m: MyNatMyNat) :MyNat: Typem *m: MyNat1 =1: MyNatm :=m: MyNatm: MyNatm * 1 = mm: MyNatm * 1 = mm: MyNatm * succ 0 = mm: MyNatm * succ 0 = mm: MyNatm * succ 0 = mm: MyNatm * 0 + m = mm: MyNatm * 0 + m = mm: MyNatm * 0 + m = mm: MyNat0 + m = mm: MyNat0 + m = mm: MyNat0 + m = mm: MyNatm = mGoals accomplished! 🐙
Notice how all our theorems are nicely building on each other.
Next up is Multiplication Level 3.
import MyNat.Addition
import MyNat.Multiplication
import AdditionWorld.Level1 -- zero_add
import AdditionWorld.Level5 -- one_eq_succ_zero, succ_eq_add_one
namespace MyNat
open MyNat
Multiplication World
Level 3: one_mul
These proofs from addition world might be useful here:
one_eq_succ_zero : 1 = succ 0
succ_eq_add_one a : succ a = a + 1
We just proved mul_one
, now let's prove one_mul
. Then we will have proved, in fancy terms, that
1 is a "left and right identity" for multiplication (just like we showed that 0 is a left and right
identity for addition with add_zero
and zero_add
).
Lemma
For any natural number m
, we have 1 * m = m.
lemmaone_mul (one_mul: ∀ (m : MyNat), 1 * m = mm :m: MyNatMyNat) :MyNat: Type1 *1: MyNatm =m: MyNatm :=m: MyNatm: MyNat1 * m = mm: MyNat1 * m = m
zero1 * zero = zero
zero1 * zero = zero
zero1 * 0 = 0
zero1 * 0 = 0
zero1 * 0 = 0
zero0 = 0Goals accomplished! 🐙m: MyNat
ih: 1 * m = m
succ1 * succ m = succ mm: MyNat
ih: 1 * m = m
succ1 * succ m = succ mm: MyNat
ih: 1 * m = m
succ1 * m + 1 = succ mm: MyNat
ih: 1 * m = m
succ1 * m + 1 = succ mm: MyNat
ih: 1 * m = m
succ1 * m + 1 = succ mm: MyNat
ih: 1 * m = m
succm + 1 = succ mm: MyNat
ih: 1 * m = m
succm + 1 = succ mm: MyNat
ih: 1 * m = m
succm + 1 = succ mm: MyNat
ih: 1 * m = m
succm + 1 = m + 1Goals accomplished! 🐙
Next up is Multiplication Level 4.
import MyNat.Addition
import MyNat.Multiplication
import AdditionWorld.Level1 -- zero_add
import AdditionWorld.Level2 -- add_assoc
namespace MyNat
open MyNat
Multiplication World
Level 4: mul_add
Where are we going? Well we want to prove mul_comm
and mul_assoc
, i.e. that a * b = b * a
and
(a * b) * c = a * (b * c)
. But we also want to
establish the way multiplication interacts with addition,
i.e. we want to prove that we can "expand out the brackets"
and show a * (b + c) = (a * b) + (a * c)
.
The technical term for this is "left distributivity of
multiplication over addition" (there is also right distributivity,
which we'll get to later).
Note the name of this proof -- mul_add
. And note the left
hand side -- a * (b + c)
, a multiplication and then an addition.
I think mul_add
is much easier to remember than "left_distrib",
an alternative name for the proof of this lemma.
Lemma
Multiplication is distributive over addition.
In other words, for all natural numbers a
, b
and t
, we have
t(a + b) = ta + tb.
lemmamul_add (mul_add: ∀ (t a b : MyNat), t * (a + b) = t * a + t * btt: MyNataa: MyNatb :b: MyNatMyNat) :MyNat: Typet * (t: MyNata +a: MyNatb) =b: MyNatt *t: MyNata +a: MyNatt *t: MyNatb :=b: MyNatt, a, b: MyNatt * (a + b) = t * a + t * bt, a, b: MyNatt * (a + b) = t * a + t * bt, a: MyNat
zerot * (a + zero) = t * a + t * zerot, a: MyNat
zerot * (a + zero) = t * a + t * zerot, a: MyNat
zerot * (a + 0) = t * a + t * 0t, a: MyNat
zerot * a = t * a + t * 0t, a: MyNat
zerot * a = t * a + 0t, a: MyNat
zerot * a = t * aGoals accomplished! 🐙t, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * (a + succ b) = t * a + t * succ bt, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * (a + succ b) = t * a + t * succ bt, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * succ (a + b) = t * a + t * succ bt, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * succ (a + b) = t * a + t * succ bt, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * succ (a + b) = t * a + t * succ bt, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * (a + b) + t = t * a + t * succ bt, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * (a + b) + t = t * a + t * succ bt, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * (a + b) + t = t * a + t * succ bt, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * a + t * b + t = t * a + t * succ bt, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * a + t * b + t = t * a + t * succ bt, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * a + t * b + t = t * a + t * succ bt, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * a + t * b + t = t * a + (t * b + t)t, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * a + t * b + t = t * a + (t * b + t)t, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * a + t * b + t = t * a + (t * b + t)t, a, b: MyNat
ih: t * (a + b) = t * a + t * b
succt * a + (t * b + t) = t * a + (t * b + t)defGoals accomplished! 🐙left_distrib :=left_distrib: ∀ (t a b : MyNat), t * (a + b) = t * a + t * bmul_add -- the "proper" name for this lemmamul_add: ∀ (t a b : MyNat), t * (a + b) = t * a + t * b
Next up is Multiplication Level 5.
import MyNat.Addition
import MyNat.Multiplication
import MultiplicationWorld.Level1 -- zero_mul
import MultiplicationWorld.Level4 -- mul_add
namespace MyNat
open MyNat
Multiplication World
Level 5: mul_assoc
We now have enough to prove that multiplication is associative.
Random tactic hints
Did you know you can repeat a tactic as many times as necessary to complete the goal?
Lemma
Multiplication is associative.
In other words, for all natural numbers a
, b
and c
, we have
(ab)c = a(bc).
lemmamul_assoc (mul_assoc: ∀ (a b c : MyNat), a * b * c = a * (b * c)aa: MyNatbb: MyNatc :c: MyNatMyNat) : (MyNat: Typea *a: MyNatb) *b: MyNatc =c: MyNata * (a: MyNatb *b: MyNatc) :=c: MyNata, b, c: MyNata * b * c = a * (b * c)a, b, c: MyNata * b * c = a * (b * c)a, b: MyNat
zeroa * b * zero = a * (b * zero)a, b: MyNat
zeroa * b * zero = a * (b * zero)a, b: MyNat
zeroa * b * 0 = a * (b * 0)a, b: MyNat
zeroa * b * 0 = a * (b * 0)a, b: MyNat
zeroa * b * 0 = a * (b * 0)a, b: MyNat
zeroa * b * 0 = a * (b * 0)Goals accomplished! 🐙a, b: MyNat
zero0 = a * 0a, b, c: MyNat
ih: a * b * c = a * (b * c)
succa * b * succ c = a * (b * succ c)a, b, c: MyNat
ih: a * b * c = a * (b * c)
succa * b * succ c = a * (b * succ c)a, b, c: MyNat
ih: a * b * c = a * (b * c)
succa * b * c + a * b = a * (b * succ c)a, b, c: MyNat
ih: a * b * c = a * (b * c)
succa * b * c + a * b = a * (b * succ c)a, b, c: MyNat
ih: a * b * c = a * (b * c)
succa * b * c + a * b = a * (b * succ c)a, b, c: MyNat
ih: a * b * c = a * (b * c)
succa * b * c + a * b = a * (b * c + b)a, b, c: MyNat
ih: a * b * c = a * (b * c)
succa * b * c + a * b = a * (b * c + b)a, b, c: MyNat
ih: a * b * c = a * (b * c)
succa * b * c + a * b = a * (b * c + b)a, b, c: MyNat
ih: a * b * c = a * (b * c)
succa * (b * c) + a * b = a * (b * c + b)a, b, c: MyNat
ih: a * b * c = a * (b * c)
succa * (b * c) + a * b = a * (b * c + b)a, b, c: MyNat
ih: a * b * c = a * (b * c)
succa * (b * c) + a * b = a * (b * c + b)a, b, c: MyNat
ih: a * b * c = a * (b * c)
succa * (b * c) + a * b = a * (b * c) + a * bGoals accomplished! 🐙
A mathematician could now remark that you have proved that the natural numbers form a monoid under multiplication.
-- instance : AddMonoid MyNat where
-- add_zero := add_zero
-- zero_add := zero_add
-- add_assoc := add_assoc
-- nsmul := λ x y => (myNatFromNat x) * y
-- nsmul_zero' := zero_mul
-- nsmul_succ' n x := by
-- simp
-- BUGBUG: complete these instances...
Next up is Multiplication Level 6.
import MyNat.Addition
import MyNat.Multiplication
import AdditionWorld.Level6 -- add_right_comm
namespace MyNat
open MyNat
Multiplication World
Level 6: succ_mul
We now begin our journey to mul_comm
, the proof that a * b = b * a
.
We'll get there in level 8. Until we're there, it is frustrating
but true that we cannot assume commutativity. We have mul_succ
but we're going to need succ_mul
(guess what it says -- maybe you
are getting the hang of Lean's naming conventions).
Remember also that we have tools like
add_right_comm a b c : a + b + c = a + c + b
These things are the tools we need to slowly build up the results
which we will need to do mathematics "normally".
We also now have access to Lean's simp
tactic,
which will solve any goal which just needs a bunch
of rewrites of add_assoc
and add_comm
. Use if
you're getting lazy!
Lemma
For all natural numbers a
and b
, we have succ a * b = ab + b.
lemmasucc_mul (succ_mul: ∀ (a b : MyNat), succ a * b = a * b + baa: MyNatb :b: MyNatMyNat) :MyNat: Typesuccsucc: MyNat → MyNata *a: MyNatb =b: MyNata *a: MyNatb +b: MyNatb :=b: MyNata, b: MyNatsucc a * b = a * b + ba, b: MyNatsucc a * b = a * b + ba: MyNat
zerosucc a * zero = a * zero + zeroa: MyNat
zerosucc a * zero = a * zero + zeroa: MyNat
zerosucc a * 0 = a * 0 + 0a: MyNat
zerosucc a * 0 = a * 0 + 0a: MyNat
zerosucc a * 0 = a * 0 + 0a: MyNat
zero0 = a * 0 + 0a: MyNat
zero0 = a * 0 + 0a: MyNat
zero0 = a * 0 + 0a: MyNat
zero0 = 0 + 0a: MyNat
zero0 = 0 + 0a: MyNat
zero0 = 0 + 0a: MyNat
zero0 = 0Goals accomplished! 🐙a, b: MyNat
ih: succ a * b = a * b + b
succsucc a * succ b = a * succ b + succ ba, b: MyNat
ih: succ a * b = a * b + b
succsucc a * succ b = a * succ b + succ ba, b: MyNat
ih: succ a * b = a * b + b
succsucc a * b + succ a = a * succ b + succ ba, b: MyNat
ih: succ a * b = a * b + b
succsucc a * b + succ a = a * succ b + succ ba, b: MyNat
ih: succ a * b = a * b + b
succsucc a * b + succ a = a * succ b + succ ba, b: MyNat
ih: succ a * b = a * b + b
succsucc a * b + succ a = a * b + a + succ ba, b: MyNat
ih: succ a * b = a * b + b
succsucc a * b + succ a = a * b + a + succ ba, b: MyNat
ih: succ a * b = a * b + b
succsucc a * b + succ a = a * b + a + succ ba, b: MyNat
ih: succ a * b = a * b + b
succa * b + b + succ a = a * b + a + succ ba, b: MyNat
ih: succ a * b = a * b + b
succa * b + b + succ a = a * b + a + succ ba, b: MyNat
ih: succ a * b = a * b + b
succa * b + b + succ a = a * b + a + succ ba, b: MyNat
ih: succ a * b = a * b + b
succsucc (a * b + b + a) = a * b + a + succ ba, b: MyNat
ih: succ a * b = a * b + b
succsucc (a * b + b + a) = a * b + a + succ ba, b: MyNat
ih: succ a * b = a * b + b
succsucc (a * b + b + a) = a * b + a + succ ba, b: MyNat
ih: succ a * b = a * b + b
succsucc (a * b + b + a) = succ (a * b + a + b)a, b: MyNat
ih: succ a * b = a * b + b
succsucc (a * b + b + a) = succ (a * b + a + b)a, b: MyNat
ih: succ a * b = a * b + b
succsucc (a * b + b + a) = succ (a * b + a + b)a, b: MyNat
ih: succ a * b = a * b + b
succsucc (a * b + a + b) = succ (a * b + a + b)Goals accomplished! 🐙
Next up is Multiplication Level 7.
import MyNat.Addition
import MyNat.Multiplication
import MultiplicationWorld.Level1 -- zero_mul
import MultiplicationWorld.Level6 -- succ_mul
namespace MyNat
open MyNat
Multiplication World
Level 7: add_mul
We proved mul_add
already, but because we don't have commutativity yet
we also need to prove add_mul
. We have a bunch of tools now, so this won't
be too hard. You know what -- you can do this one by induction on any of
the variables. Try them all! Which works best? If you can't face
doing all the commutativity and associativity, remember the high-powered
simp
tactic mentioned at the bottom of Addition World level 6,
which will solve any puzzle which needs only commutativity
and associativity. If your goal looks like a+(b+c)=c+b+a
or something,
don't mess around doing it explicitly with add_comm
and add_assoc
,
just try simp
.
Lemma
Addition is distributive over multiplication.
In other words, for all natural numbers a
, b
and t
, we have
(a + b) * t = at + bt.
lemmaadd_mul (add_mul: ∀ (a b t : MyNat), (a + b) * t = a * t + b * taa: MyNatbb: MyNatt :t: MyNatMyNat) : (MyNat: Typea +a: MyNatb) *b: MyNatt =t: MyNata *a: MyNatt +t: MyNatb *b: MyNatt :=t: MyNata, b, t: MyNat(a + b) * t = a * t + b * ta, b, t: MyNat(a + b) * t = a * t + b * ta, t: MyNat
zero(a + zero) * t = a * t + zero * ta, t: MyNat
zero(a + zero) * t = a * t + zero * ta, t: MyNat
zero(a + 0) * t = a * t + 0 * ta, t: MyNat
zero(a + 0) * t = a * t + 0 * ta, t: MyNat
zero(a + 0) * t = a * t + 0 * ta, t: MyNat
zero(a + 0) * t = a * t + 0a, t: MyNat
zero(a + 0) * t = a * t + 0a, t: MyNat
zero(a + 0) * t = a * t + 0a, t: MyNat
zeroa * t = a * t + 0a, t: MyNat
zeroa * t = a * t + 0a, t: MyNat
zeroa * t = a * t + 0a, t: MyNat
zeroa * t = a * tGoals accomplished! 🐙a, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succ(a + succ b) * t = a * t + succ b * ta, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succ(a + succ b) * t = a * t + succ b * ta, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succsucc (a + b) * t = a * t + succ b * ta, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succsucc (a + b) * t = a * t + succ b * ta, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succsucc (a + b) * t = a * t + succ b * ta, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succ(a + b) * t + t = a * t + succ b * ta, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succ(a + b) * t + t = a * t + succ b * ta, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succ(a + b) * t + t = a * t + succ b * ta, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succa * t + b * t + t = a * t + succ b * ta, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succa * t + b * t + t = a * t + succ b * ta, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succa * t + b * t + t = a * t + succ b * ta, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succa * t + b * t + t = a * t + (b * t + t)a, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succa * t + b * t + t = a * t + (b * t + t)a, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succa * t + b * t + t = a * t + (b * t + t)a, t, b: MyNat
ih: (a + b) * t = a * t + b * t
succa * t + (b * t + t) = a * t + (b * t + t)Goals accomplished! 🐙
A mathematician would now say that you have proved that the natural numbers are a semiring. This sounds like a respectable result.
def right_distrib: ∀ (a b t : MyNat), (a + b) * t = a * t + b * t
right_distrib := add_mul: ∀ (a b t : MyNat), (a + b) * t = a * t + b * t
add_mul -- alternative name
-- instance : semiring MyNat := by structure_helper
-- BUGBUG
Lean would add that you have also proved that they are a distrib
.
However this concept has no mathematical name at all -- this says something
about the regard with which mathematicians hold this collectible.
This is an artifact of the set-up of collectibles in Lean. You consider politely
declining Lean's offer of a distrib
collectible.
You are dreaming of the big collectible at the end of Power World.
-- instance : distrib MyNat := by structure_helper --
-- BUGBUG
On to Level 8
import MyNat.Addition
import MyNat.Multiplication
import MultiplicationWorld.Level1
import MultiplicationWorld.Level6
namespace MyNat
open MyNat
Multiplication World
Level 8: mul_comm
Finally, the boss level of multiplication world. But you are well-prepared for it -- you have
zero_mul
and mul_zero
, as well as succ_mul
and mul_succ
. After this level you can of course
throw away one of each pair if you like, but I would recommend you hold on to them, sometimes it's
convenient to have exactly the right tools to do a job.
Lemma
Multiplication is commutative.
lemmamul_comm (mul_comm: ∀ (a b : MyNat), a * b = b * aaa: MyNatb :b: MyNatMyNat) :MyNat: Typea *a: MyNatb =b: MyNatb *b: MyNata :=a: MyNata, b: MyNata * b = b * aa, b: MyNata * b = b * aa: MyNat
zeroa * zero = zero * aa: MyNat
zeroa * zero = zero * aa: MyNat
zeroa * 0 = 0 * aa: MyNat
zeroa * 0 = 0 * aa: MyNat
zeroa * 0 = 0 * aa: MyNat
zeroa * 0 = 0a: MyNat
zeroa * 0 = 0a: MyNat
zeroa * 0 = 0a: MyNat
zero0 = 0Goals accomplished! 🐙a, b: MyNat
ih: a * b = b * a
succa * succ b = succ b * aa, b: MyNat
ih: a * b = b * a
succa * succ b = succ b * aa, b: MyNat
ih: a * b = b * a
succa * succ b = b * a + aa, b: MyNat
ih: a * b = b * a
succa * succ b = b * a + aa, b: MyNat
ih: a * b = b * a
succa * succ b = b * a + aa, b: MyNat
ih: a * b = b * a
succa * succ b = a * b + aa, b: MyNat
ih: a * b = b * a
succa * succ b = a * b + aa, b: MyNat
ih: a * b = b * a
succa * succ b = a * b + aa, b: MyNat
ih: a * b = b * a
succa * b + a = a * b + aGoals accomplished! 🐙
You've now proved that the natural numbers are a commutative semiring! That's the last collectible in Multiplication World.
-- instance MyNat.comm_semiring : comm_semiring MyNat := by structure_helper
-- BUGBUG
But don't leave multiplication just yet -- prove mul_left_comm
, the last
level of the world, and then we can beef up the power of simp
.
On to Level 9
import MyNat.Addition
import MyNat.Multiplication
import MultiplicationWorld.Level5
import MultiplicationWorld.Level8
namespace MyNat
open MyNat
Multiplication World
Level 9: mul_left_comm
You are equipped with
mul_assoc (a b c : MyNat) : (a * b) * c = a * (b * c)
mul_comm (a b : MyNat) : a * b = b * a
Re-read the docs for rw
so you know all the tricks.
You can see them in the Tactics section on the left.
Lemma
For all natural numbers a
b
and c
, we have a(bc) = b(ac)
lemmamul_left_comm (mul_left_comm: ∀ (a b c : MyNat), a * (b * c) = b * (a * c)aa: MyNatbb: MyNatc :c: MyNatMyNat) :MyNat: Typea * (a: MyNatb *b: MyNatc) =c: MyNatb * (b: MyNata *a: MyNatc) :=c: MyNata, b, c: MyNata * (b * c) = b * (a * c)a, b, c: MyNata * (b * c) = b * (a * c)a, b, c: MyNata * b * c = b * (a * c)a, b, c: MyNata * b * c = b * (a * c)a, b, c: MyNata * b * c = b * (a * c)a, b, c: MyNatb * a * c = b * (a * c)a, b, c: MyNatb * a * c = b * (a * c)a, b, c: MyNatb * a * c = b * (a * c)a, b, c: MyNatb * (a * c) = b * (a * c)Goals accomplished! 🐙
And now you can teach the simp
tactic these new tricks:
attribute [simp] mul_assoc: ∀ (a b c : MyNat), a * b * c = a * (b * c)
mul_assoc mul_comm: ∀ (a b : MyNat), a * b = b * a
mul_comm mul_left_comm: ∀ (a b c : MyNat), a * (b * c) = b * (a * c)
mul_left_comm
and all of a sudden Lean can automatically do levels which are very boring for a human, for example:
example (example: ∀ (a b c d e : MyNat), a * b * c * d * e = c * (b * e * a) * daa: MyNatbb: MyNatcc: MyNatdd: MyNate :e: MyNatMyNat) : (((MyNat: Typea*a: MyNatb)*b: MyNatc)*c: MyNatd)*d: MyNate=(e: MyNatc*((c: MyNatb*b: MyNate)*e: MyNata))*a: MyNatd :=d: MyNata, b, c, d, e: MyNata * b * c * d * e = c * (b * e * a) * dGoals accomplished! 🐙
If you feel like attempting Advanced Multiplication world you'll have to do Function World and the Proposition Worlds first. These worlds assume a certain amount of mathematical maturity (perhaps 1st year undergraduate level).
Your other possibility is Power World.
import PowerWorld.Level1
import PowerWorld.Level2
import PowerWorld.Level3
import PowerWorld.Level4
import PowerWorld.Level5
import PowerWorld.Level6
import PowerWorld.Level7
import PowerWorld.Level8
Power World
A new world with seven levels. And a new import! This import gives you the power to make powers of your natural numbers. It is defined by recursion, just like addition and multiplication. Here are the two new axioms:
pow_zero (a : MyNat) : a ^ 0 = 1
pow_succ (a b : MyNat) : a ^ succ b = a ^ b * a
The power function has various relations to addition and multiplication.
If you have gone through levels 1--6 of Addition World and
levels 1--9 of Multiplication World, you should have no trouble with this world:
The usual tactics induction
, rw
and rfl
should see you through.
Addition and multiplication -- we have a solid API for them now, i.e. if you need something about addition or multiplication, it's probably already in the library we have built. Collectibles are an indication that we are proving the right things.
The levels in this world were designed by Sian Carey, a UROP student at Imperial College London, funded by a Mary Lister McCammon Fellowship, in the summer of 2019. Thanks Sian!
Let's begin with Level 1.
import MyNat.Power
namespace MyNat
open MyNat
Level 1: zero_pow_zero
Given the lemma pow_zero
which says m ^ 0 = 1
you can now prove zero to the power of zero is also one.
Lemma
0 ^ 0 = 1
.
lemmazero_pow_zero : (zero_pow_zero: 0 ^ 0 = 10 :0: MyNatMyNat) ^ (MyNat: Type0 :0: MyNatMyNat) =MyNat: Type1 :=1: MyNat0 ^ 0 = 10 ^ 0 = 11 = 1Goals accomplished! 🐙
That was easy! Next up Level 2
import PowerWorld.Level1
import MyNat.Multiplication -- mul_zero
namespace MyNat
open MyNat
Power World
Level 2: zero_pow_succ
Lemma
For all naturals m
, 0 ^ (succ m) = 0
.
lemmazero_pow_succ (zero_pow_succ: ∀ (m : MyNat), 0 ^ succ m = 0m :m: MyNatMyNat) : (MyNat: Type0 :0: MyNatMyNat) ^ (MyNat: Typesuccsucc: MyNat → MyNatm) =m: MyNat0 :=0: MyNatm: MyNat0 ^ succ m = 0m: MyNat0 ^ succ m = 0m: MyNat0 ^ m * 0 = 0m: MyNat0 ^ m * 0 = 0m: MyNat0 ^ m * 0 = 0m: MyNat0 = 0Goals accomplished! 🐙
Next up Level 3
import MyNat.Power
import AdditionWorld.Level5 -- one_eq_succ_zero
import MultiplicationWorld.Level3 -- one_mul
namespace MyNat
open MyNat
Power World
Level 3: pow_one
Lemma
For all naturals a
, a ^ 1 = a
.
lemmapow_one (pow_one: ∀ (a : MyNat), a ^ 1 = aa :a: MyNatMyNat) :MyNat: Typea ^ (a: MyNat1 :1: MyNatMyNat) =MyNat: Typea :=a: MyNata: MyNata ^ 1 = aa: MyNata ^ 1 = aa: MyNata ^ succ 0 = aa: MyNata ^ succ 0 = aa: MyNata ^ succ 0 = aa: MyNata ^ 0 * a = aa: MyNata ^ 0 * a = aa: MyNata ^ 0 * a = aa: MyNat1 * a = aa: MyNat1 * a = aa: MyNat1 * a = aa: MyNata = aGoals accomplished! 🐙
Next up Level 4
import MyNat.Power
import MultiplicationWorld.Level2 -- mul_one
namespace MyNat
open MyNat
Power World
Level 4: one_pow
Lemma
For all naturals m
, 1 ^ m = 1
.
lemmaone_pow (one_pow: ∀ (m : MyNat), 1 ^ m = 1m :m: MyNatMyNat) : (MyNat: Type1 :1: MyNatMyNat) ^MyNat: Typem =m: MyNat1 :=1: MyNatm: MyNat1 ^ m = 1m: MyNat1 ^ m = 1
zero1 ^ zero = 1
zero1 ^ zero = 1
zero1 ^ 0 = 1
zero1 ^ 0 = 1
zero1 ^ 0 = 1
zero1 = 1Goals accomplished! 🐙m: MyNat
ih: 1 ^ m = 1
succ1 ^ succ m = 1m: MyNat
ih: 1 ^ m = 1
succ1 ^ succ m = 1m: MyNat
ih: 1 ^ m = 1
succ1 ^ m * 1 = 1m: MyNat
ih: 1 ^ m = 1
succ1 ^ m * 1 = 1m: MyNat
ih: 1 ^ m = 1
succ1 ^ m * 1 = 1m: MyNat
ih: 1 ^ m = 1
succ1 * 1 = 1m: MyNat
ih: 1 ^ m = 1
succ1 * 1 = 1m: MyNat
ih: 1 ^ m = 1
succ1 * 1 = 1m: MyNat
ih: 1 ^ m = 1
succ1 = 1Goals accomplished! 🐙
Next up Level 5
import MyNat.Power
import MyNat.Addition -- add_zero
import MultiplicationWorld.Level2 -- mul_one
import MultiplicationWorld.Level5 -- mul_assoc
namespace MyNat
open MyNat
Power World
Level 5: pow_add
Lemma
For all naturals a
, m
, n
, we have a^(m + n) = a ^ m a ^ n
.
lemmapow_add (pow_add: ∀ (a m n : MyNat), a ^ (m + n) = a ^ m * a ^ naa: MyNatmm: MyNatn :n: MyNatMyNat) :MyNat: Typea ^ (a: MyNatm +m: MyNatn) =n: MyNata ^a: MyNatm *m: MyNata ^a: MyNatn :=n: MyNata, m, n: MyNata ^ (m + n) = a ^ m * a ^ na, m, n: MyNata ^ (m + n) = a ^ m * a ^ na, m: MyNat
zeroa ^ (m + zero) = a ^ m * a ^ zeroa, m: MyNat
zeroa ^ (m + zero) = a ^ m * a ^ zeroa, m: MyNat
zeroa ^ (m + 0) = a ^ m * a ^ 0a, m: MyNat
zeroa ^ (m + 0) = a ^ m * a ^ 0a, m: MyNat
zeroa ^ (m + 0) = a ^ m * a ^ 0a, m: MyNat
zeroa ^ m = a ^ m * a ^ 0a, m: MyNat
zeroa ^ m = a ^ m * a ^ 0a, m: MyNat
zeroa ^ m = a ^ m * a ^ 0a, m: MyNat
zeroa ^ m = a ^ m * 1a, m: MyNat
zeroa ^ m = a ^ m * 1a, m: MyNat
zeroa ^ m = a ^ m * 1a, m: MyNat
zeroa ^ m = a ^ mGoals accomplished! 🐙a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ (m + succ n) = a ^ m * a ^ succ na, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ (m + succ n) = a ^ m * a ^ succ na, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ succ (m + n) = a ^ m * a ^ succ na, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ succ (m + n) = a ^ m * a ^ succ na, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ succ (m + n) = a ^ m * a ^ succ na, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ (m + n) * a = a ^ m * a ^ succ na, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ (m + n) * a = a ^ m * a ^ succ na, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ (m + n) * a = a ^ m * a ^ succ na, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ (m + n) * a = a ^ m * (a ^ n * a)a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ (m + n) * a = a ^ m * (a ^ n * a)a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ (m + n) * a = a ^ m * (a ^ n * a)a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ m * a ^ n * a = a ^ m * (a ^ n * a)a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ m * a ^ n * a = a ^ m * (a ^ n * a)a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ m * a ^ n * a = a ^ m * (a ^ n * a)a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n
succa ^ m * (a ^ n * a) = a ^ m * (a ^ n * a)Goals accomplished! 🐙
Remember you can combine all the rw
rules into one with
rw [add_succ, pow_succ, pow_succ, ih, mul_assoc]
but we have
broken it out here so you can more easily see all the intermediate
goal states.
Next up Level 6
import MyNat.Power
import MultiplicationWorld.Level2 -- mul_one
import MultiplicationWorld.Level9 -- simp additions
namespace MyNat
open MyNat
Power World
Level 6: mul_pow
Here we use the attribute [simp]
additions we made in
level 9 of Multiplication World
so that the simp
tactic can simplify expressions involving *
.
Lemma
For all naturals a
, b
, n
, we have (ab) ^ n = a ^ nb ^ n
.
lemmamul_pow (mul_pow: ∀ (a b n : MyNat), (a * b) ^ n = a ^ n * b ^ naa: MyNatbb: MyNatn :n: MyNatMyNat) : (MyNat: Typea *a: MyNatb) ^b: MyNatn =n: MyNata ^a: MyNatn *n: MyNatb ^b: MyNatn :=n: MyNata, b, n: MyNat(a * b) ^ n = a ^ n * b ^ na, b, n: MyNat(a * b) ^ n = a ^ n * b ^ na, b: MyNat
zero(a * b) ^ zero = a ^ zero * b ^ zeroa, b: MyNat
zero(a * b) ^ zero = a ^ zero * b ^ zeroa, b: MyNat
zero(a * b) ^ 0 = a ^ 0 * b ^ 0a, b: MyNat
zero(a * b) ^ 0 = a ^ 0 * b ^ 0a, b: MyNat
zero(a * b) ^ 0 = a ^ 0 * b ^ 0a, b: MyNat
zero1 = 1 * b ^ 0a, b: MyNat
zero1 = a ^ 0 * b ^ 0a, b: MyNat
zero1 = 1 * 1a, b: MyNat
zero1 = 1 * 1a, b: MyNat
zero1 = 1Goals accomplished! 🐙a, b, n: MyNat
ih: (a * b) ^ n = a ^ n * b ^ n
succ(a * b) ^ succ n = a ^ succ n * b ^ succ na, b, n: MyNat
ih: (a * b) ^ n = a ^ n * b ^ n
succ(a * b) ^ succ n = a ^ succ n * b ^ succ na, b, n: MyNat
ih: (a * b) ^ n = a ^ n * b ^ n
succ(a * b) ^ n * (a * b) = a ^ succ n * b ^ succ na, b, n: MyNat
ih: (a * b) ^ n = a ^ n * b ^ n
succ(a * b) ^ n * (a * b) = a ^ n * a * (b ^ n * b)a, b, n: MyNat
ih: (a * b) ^ n = a ^ n * b ^ n
succ(a * b) ^ n * (a * b) = a ^ n * a * (b ^ n * b)a, b, n: MyNat
ih: (a * b) ^ n = a ^ n * b ^ n
succ(a * b) ^ n * (a * b) = a ^ n * a * (b ^ n * b)a, b, n: MyNat
ih: (a * b) ^ n = a ^ n * b ^ n
succa ^ n * b ^ n * (a * b) = a ^ n * a * (b ^ n * b)a, b, n: MyNat
ih: (a * b) ^ n = a ^ n * b ^ n
succa ^ n * b ^ n * (a * b) = a ^ n * a * (b ^ n * b)Goals accomplished! 🐙
Next up Level 7
import MyNat.Power
import MyNat.Multiplication
import PowerWorld.Level5 -- pow_add
namespace MyNat
open MyNat
Power World
Level 7: pow_pow
Boss level! What will the collectible be?
Lemma
For all naturals a
, m
, n
, we have (a ^ m) ^ n = a ^ {mn}
.
lemmapow_pow (pow_pow: ∀ (a m n : MyNat), (a ^ m) ^ n = a ^ (m * n)aa: MyNatmm: MyNatn :n: MyNatMyNat) : (MyNat: Typea ^a: MyNatm) ^m: MyNatn =n: MyNata ^ (a: MyNatm *m: MyNatn) :=n: MyNata, m, n: MyNat(a ^ m) ^ n = a ^ (m * n)a, m, n: MyNat(a ^ m) ^ n = a ^ (m * n)a, m: MyNat
zero(a ^ m) ^ zero = a ^ (m * zero)a, m: MyNat
zero(a ^ m) ^ zero = a ^ (m * zero)a, m: MyNat
zero(a ^ m) ^ 0 = a ^ (m * 0)a, m: MyNat
zero(a ^ m) ^ 0 = a ^ (m * 0)a, m: MyNat
zero(a ^ m) ^ 0 = a ^ (m * 0)a, m: MyNat
zero(a ^ m) ^ 0 = a ^ 0a, m: MyNat
zero(a ^ m) ^ 0 = a ^ 0a, m: MyNat
zero(a ^ m) ^ 0 = a ^ 0a, m: MyNat
zero1 = a ^ 0Goals accomplished! 🐙Goals accomplished! 🐙a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)
succ(a ^ m) ^ succ n = a ^ (m * succ n)a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)
succ(a ^ m) ^ succ n = a ^ (m * succ n)a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)
succ(a ^ m) ^ n * a ^ m = a ^ (m * succ n)a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)
succ(a ^ m) ^ n * a ^ m = a ^ (m * succ n)a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)
succ(a ^ m) ^ n * a ^ m = a ^ (m * succ n)a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)
succa ^ (m * n) * a ^ m = a ^ (m * succ n)a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)
succa ^ (m * n) * a ^ m = a ^ (m * succ n)a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)
succa ^ (m * n) * a ^ m = a ^ (m * succ n)a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)
succa ^ (m * n) * a ^ m = a ^ (m * n + m)a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)
succa ^ (m * n) * a ^ m = a ^ (m * n + m)a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)
succa ^ (m * n) * a ^ m = a ^ (m * n + m)a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)
succa ^ (m * n) * a ^ m = a ^ (m * n) * a ^ mGoals accomplished! 🐙
Apparently Lean can't find a collectible, even though you feel like you just finished power world so you must have proved something. What should the collectible for this level be called?
But what is this? It's one of those twists where there's another boss after the boss you thought was the final boss! Go to the next level!
Next up Level 8
import MyNat.Power
import AdditionWorld.Level5 -- one_eq_succ_zero
import PowerWorld.Level3 -- pow_one
import AdditionWorld.Level6 -- simp additions
import MultiplicationWorld.Level7 -- add_mul
import MultiplicationWorld.Level9 -- simp additions
namespace MyNat
open MyNat
Power World
Level 8: add_squared
Theorem
For all naturals a
and b
, we have (a + b)^2 = a^2 + b^2 + 2ab.
The first step in writing this proof is to convert 2
into something we
have theorems about, which is 1
and 0
.
deftwo :two: MyNatMyNat :=MyNat: Type2 def2: MyNattwo_eq_succ_one :two_eq_succ_one: two = succ 1two =two: MyNatsuccsucc: MyNat → MyNat1 :=1: MyNattwo = succ 1lemmaGoals accomplished! 🐙one_plus_one : (one_plus_one: 1 + 1 = 21 :1: MyNatMyNat) + (MyNat: Type1 :1: MyNatMyNat) = (MyNat: Type2 :2: MyNatMyNat) :=MyNat: Type1 + 1 = 2-- and we already have one_eq_succ_zero.Goals accomplished! 🐙
Now we are ready to tackle the proof:
lemmaadd_squared (add_squared: ∀ (a b : MyNat), (a + b) ^ two = a ^ two + b ^ two + 2 * a * baa: MyNatb :b: MyNatMyNat) : (MyNat: Typea +a: MyNatb) ^b: MyNattwo =two: MyNata ^a: MyNattwo +two: MyNatb ^b: MyNattwo +two: MyNat2 *2: MyNata *a: MyNatb :=b: MyNata, b: MyNat(a + b) ^ two = a ^ two + b ^ two + 2 * a * ba, b: MyNat(a + b) ^ two = a ^ two + b ^ two + 2 * a * ba, b: MyNat(a + b) ^ succ 1 = a ^ succ 1 + b ^ succ 1 + 2 * a * ba, b: MyNat(a + b) ^ succ 1 = a ^ succ 1 + b ^ succ 1 + 2 * a * ba, b: MyNat(a + b) ^ succ 1 = a ^ succ 1 + b ^ succ 1 + 2 * a * ba, b: MyNat(a + b) ^ succ (succ 0) = a ^ succ (succ 0) + b ^ succ (succ 0) + 2 * a * ba, b: MyNat(a + b) ^ succ (succ 0) = a ^ succ (succ 0) + b ^ succ (succ 0) + 2 * a * ba, b: MyNat(a + b) ^ succ (succ 0) = a ^ succ (succ 0) + b ^ succ (succ 0) + 2 * a * ba, b: MyNat(a + b) ^ 0 * (a + b) * (a + b) = a ^ 0 * a * a + b ^ succ (succ 0) + 2 * a * ba, b: MyNat(a + b) ^ 0 * (a + b) * (a + b) = a ^ 0 * a * a + b ^ succ 0 * b + 2 * a * ba, b: MyNat(a + b) ^ 0 * (a + b) * (a + b) = a ^ succ 0 * a + b ^ succ (succ 0) + 2 * a * ba, b: MyNat(a + b) ^ 0 * (a + b) * (a + b) = a ^ 0 * a * a + b ^ 0 * b * b + 2 * a * ba, b: MyNat1 * (a + b) * (a + b) = a ^ 0 * a * a + b ^ 0 * b * b + 2 * a * ba, b: MyNat1 * (a + b) * (a + b) = 1 * a * a + 1 * b * b + 2 * a * ba, b: MyNat1 * (a + b) * (a + b) = 1 * a * a + b ^ 0 * b * b + 2 * a * ba, b: MyNat1 * (a + b) * (a + b) = 1 * a * a + 1 * b * b + 2 * a * ba, b: MyNat(a + b) * (a + b) = a * a + 1 * b * b + 2 * a * ba, b: MyNat(a + b) * (a + b) = a * a + 1 * b * b + 2 * a * ba, b: MyNat(a + b) * (a + b) = a * a + 1 * b * b + 2 * a * ba, b: MyNat(a + b) * (a + b) = a * a + b * b + 2 * a * ba, b: MyNat(a + b) * a + (a + b) * b = a * a + b * b + 2 * a * ba, b: MyNat(a + b) * a + (a + b) * b = a * a + b * b + 2 * a * ba, b: MyNat(a + b) * a + (a + b) * b = a * a + b * b + 2 * a * ba, b: MyNata * a + b * a + (a * b + b * b) = a * a + b * b + 2 * a * ba, b: MyNata * a + b * a + (a * b + b * b) = a * a + b * b + 2 * a * ba, b: MyNata * a + b * a + (a + b) * b = a * a + b * b + 2 * a * ba, b: MyNata * a + b * a + (a * b + b * b) = a * a + b * b + 2 * a * ba, b: MyNata * a + b * a + (a * b + b * b) = a * a + b * b + (1 + 1) * a * ba, b: MyNata * a + b * a + (a * b + b * b) = a * a + b * b + (1 + 1) * a * ba, b: MyNata * a + b * a + (a * b + b * b) = a * a + b * b + (1 + 1) * a * ba, b: MyNata * a + b * a + (a * b + b * b) = a * a + b * b + (1 + 1) * a * ba, b: MyNata * a + b * a + (a * b + b * b) = a * a + b * b + (1 * a * b + 1 * a * b)a, b: MyNata * a + b * a + (a * b + b * b) = a * a + b * b + (1 * a + 1 * a) * ba, b: MyNata * a + b * a + (a * b + b * b) = a * a + b * b + (1 * a * b + 1 * a * b)a, b: MyNata * a + b * a + (a * b + b * b) = a * a + b * b + (1 * a * b + 1 * a * b)a, b: MyNata * a + b * a + (a * b + b * b) = a * a + b * b + (a * b + a * b)a, b: MyNata * a + b * a + (a * b + b * b) = a * a + b * b + (a * b + a * b)Goals accomplished! 🐙
It is also helpful to teach simp
our new tricks:
attribute [simp] pow_succ: ∀ (m n : MyNat), m ^ succ n = m ^ n * m
pow_succ pow_one: ∀ (a : MyNat), a ^ 1 = a
pow_one pow_zero: ∀ (m : MyNat), m ^ 0 = 1
pow_zero
There is some fun discussion on Lean3 Zulip
about different ways to solve this one in fewer steps.
Feel free to try some of those solutions here, just note that the Lean 4 syntax is a bit different,
no commands between tactics, and square brackets are required on the rw
tactic.
Do you fancy doing (a+b)^3
now? You might want to read
this Xena Project blog post before you start though.
If you got this far -- very well done! If you only learnt the three
tactics rw
, induction
and refl
then there are now more tactics to
learn; time to try Function World.
The main thing we really want to impress upon people is that we believe that all of pure mathematics can be done in this new way.
The Liquid Tensor Experiment shows that Lean3 could be used to prove very large math theorems.
Lean 3 also has a definition of perfectoid spaces (a very complex modern mathematical structure). We believe that these systems will one day cause a paradigm shift in the way mathematics is done, but first we need to build what we know, or at least build enough to state what we mathematicians believe.
If you want to get involved, come and join us at the Zulip Lean chat. The #new members stream is a great place to start asking questions.
Next up Function World.
import FunctionWorld.Level1
import FunctionWorld.Level2
import FunctionWorld.Level3
import FunctionWorld.Level4
import FunctionWorld.Level5
import FunctionWorld.Level6
import FunctionWorld.Level7
import FunctionWorld.Level8
import FunctionWorld.Level9
Function world.
If you have finished playing with Addition World, then you have got quite
good at manipulating equalities in Lean using the rw
tactic. But there are plenty of levels later
on which will require you to manipulate functions, and rw
is not the tool for you here.
To manipulate functions effectively, we need to learn about a new collection of tactics, namely
exact
, intro
, have
and apply
. These tactics are specially designed for dealing with
functions. Of course we are ultimately interested in using these tactics to prove theorems about the
natural numbers – but in this world there is little point in working with specific sets like
MyNat
, everything works for general sets.
So our notation for this level is: P
, Q
, R
and so on denote general sets,
and h
, j
, k
and so on denote general
functions between them. What we will learn in this world is how to use functions
in Lean to push elements from set to set. A word of warning –
even though there's no harm at all in thinking of P
being a set and p
being an element, you will not see Lean using the notation p ∈ P
, because
internally Lean stores P
as a "Type" and p
as a "term", and it uses p : P
to mean "p
is a term of type P
", Lean's way of expressing the idea that p
is an element of the set P
. You have seen this already – Lean has
been writing n : MyNat
to mean that n
is a natural number.
A new kind of goal.
All through addition world, our goals have been theorems,
and it was our job to find the proofs.
The levels in function world aren't theorems. This is the only world where
the levels aren't theorems in fact. In function world the object of a level
is to create an element of the set in the goal. The goal will look like ⊢ X
with X
a set and you get rid of the goal by constructing an element of X
.
I don't know if you noticed this, but you finished
essentially every goal of Addition World (and Multiplication World and Power World,
) with rfl
or the implied rfl
performed by rw
.
This tactic is no use to us here.
We are going to have to learn a new way of solving goals – the exact
tactic.
Let's begin with Level 1.
import MyNat.Definition
namespace MyNat
open MyNat
Function World
Level 1: the exact
tactic.
Given an element of P
and a function from P
to Q
,
we define an element of Q
.
example (example: (P Q : Type) → P → (P → Q) → QPP: TypeQ :Q: TypeType) (Type: Type 1p :p: PP) (P: Typeh :h: P → QP →P: TypeQ) :Q: TypeQ :=Q: TypeP, Q: Type
p: P
h: P → QQGoals accomplished! 🐙
Note that example
is just like a theorem
or a lemma
except it has no name.
If you place your cursor at the end of the example
line above
the tactic state will look like this:
P Q : Type,
p : P,
h : P → Q
⊢ Q
In this situation, we have sets P
and Q
(but Lean calls them types),
and an element p
of P
(written p : P
but meaning p ∈ P
). We also have a function h
from P
to Q
,
and our goal is to construct an
element of the set Q
. It's clear what to do mathematically to solve
this goal -- we can
make an element of Q
by applying the function h
to
the element p
. But how to do it in Lean? There are at least two ways
to explain this idea to Lean,
and here we will learn about one of them, namely the method which
uses the exact
tactic.
The exact
tactic.
If you can explicitly see how to make an element of your goal set,
i.e. you have a formula for it, then you can just write exact <formula>
and this will close the goal.
So given that the function application h p
is an element of Q
so you can just write
exact h p
to close the goal.
Important note
Note that exact h P
won't work (with a capital P
);
this is a common error for beginners.
P
is not an element of P
, it's p
that is an element of P
.
Summary
If the goal is ⊢ X
then exact x
will close the goal if
and only if x
is a term of type X
.
Details
Say P
, Q
and R
are types (i.e., what a mathematician
might think of as either sets or propositions),
and the local context looks like this:
p : P,
h : P → Q,
j : Q → R
⊢ R
If you can spot how to make a term of type R
, then you
can just make it and say you're done using the exact
tactic
together with the formula you have spotted. For example the
above goal could be solved with
exact j (h p)
because j (h p)
is easily checked to be a term of type R
(i.e., an element of the set R
, or a proof of the proposition R
).
Next up Level 2
import MyNat.Addition
import MyNat.Multiplication
namespace MyNat
open MyNat
Function world.
Level 2: the intro
tactic.
Let's make a function. Let's define the function on the natural
numbers which sends a natural number n
to 3n+2
. In the example
below you will see that our goal is ⊢ MyNat → MyNat
. A mathematician
might denote this set with some exotic name such as Hom(ℕ,ℕ)
,
but computer scientists use notation X → Y
to denote the set of
functions from X
to Y
and this name definitely has its merits.
In type theory, X → Y
is a type (the type of all functions from X
to Y
),
and f : X → Y
means that f
is a term
of this type, i.e., f
is a function from X
to Y
.
To define a function X → Y
we need to choose an arbitrary
element x ∈ X
and then, perhaps using x
, make an element of Y
.
The Lean tactic for "let x ∈ X
be arbitrary" is intro x
.
Rule of thumb:
If your goal is P → Q
then intro p
will make progress.
To solve the goal below, you have to come up with a function from MyNat
to MyNat
. We can start with intro n
(i.e. "let n ∈ ℕ
be arbitrary") and note that our
local context now looks like this:
n : MyNat
⊢ MyNat
Our job now is to construct a natural number, which is
allowed to depend on n
. We can do this using exact
and
writing a formula for the function we want to define. For example
we imported addition and multiplication at the top of this file,
so exact 3*n+2
will close the goal, ultimately defining the function f(n)=3n+2
.
Definition
We define a function from MyNat to MyNat.
example :example: MyNat → MyNatMyNat →MyNat: TypeMyNat :=MyNat: TypeMyNat → MyNatn: MyNatMyNatGoals accomplished! 🐙
You can hover your mouse over the tactics intro
and exact
to the documentation on these tactics in case you need a
reminder later on.
See also intro tactic
Next up Level 3
import MyNat.Definition
namespace MyNat
open MyNat
Function World
Level 3: the have
tactic.
Say you have a whole bunch of sets and functions between them,
and your goal is to build a certain element of a certain set.
If it helps, you can build intermediate elements of other sets
along the way, using the have tactic. have
is the Lean analogue
of saying "let's define an element q ∈ Q
by..." in the middle of a calculation.
It is often not logically necessary, but on the other hand
it is very convenient, for example it can save on notation, or
it can break proofs or calculations up into smaller steps.
In this level, we have an element of P
and we want an element
of U
; during the proof we will make several intermediate elements
of some of the other sets involved. The diagram of sets and
functions looks like this pictorially:
and so it's clear how to make the element of U
from the element of P.
Indeed, we could solve this level in one move by typing
exact l (j (h p))
But let us instead stroll more lazily through the level.
We can start by using the have
tactic to make an element of Q
:
have q := h p
and then we note that j q
is an element of T
have t : T := j q
(notice how on this occasion we explicitly told Lean what set we thought t
was in, with
that : T
thing before the :=
) and we could even define u
to be l t
:
have u : U := l t
and then finish the level with exact u
.
Definition
Given an element of P
we can define an element of U
.
example (example: (P Q R S T U : Type) → P → (P → Q) → (Q → R) → (Q → T) → (S → T) → (T → U) → UPP: TypeQQ: TypeRR: TypeSS: TypeTT: TypeU:U: TypeType) (Type: Type 1p :p: PP) (P: Typeh :h: P → QP →P: TypeQ) (Q: Type:Q →Q: TypeR) (R: Typej :j: Q → TQ →Q: TypeT) (T: Type:S →S: TypeT) (T: Typel :l: T → UT →T: TypeU) :U: TypeU :=U: TypeP, Q, R, S, T, U: Type
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → UUP, Q, R, S, T, U: Type
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → U
q: QUP, Q, R, S, T, U: Type
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → U
q: Q
t: TUP, Q, R, S, T, U: Type
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → U
q: Q
t: T
u: UUGoals accomplished! 🐙
Remember you can move your cursor around with the arrow keys and explore the various tactic states
in this proof in Visual Studio Code, and note that the tactic state at the beginning of exact u
is
this mess:
P Q R S T U : Type,
p : P,
h : P → Q,
i : Q → R,
j : Q → T,
k : S → T,
l : T → U,
q : Q,
t : T,
u : U
⊢ U
It was already bad enough to start with, and we added three more
terms to it. In level 4 we will learn about the apply
tactic
which solves the level using another technique, without leaving
so much junk behind.
Next up Level 4
import MyNat.Definition
namespace MyNat
open MyNat
Function World
Level 4: the apply
tactic.
Let's do the same level again a different way:
We are given p ∈ P
and our goal is to find an element of U
, or
in other words to find a path through the maze that links P
to U
.
In level 3 we solved this by using have
s to move forward, from P
to Q
to T
to U
.
Using the apply tactic we can instead construct
the path backwards, moving from U
to T
to Q
to P
.
Our goal is to construct an element of the set U
. But l:T → U
is
a function, so it would suffice to construct an element of T
. Tell
Lean this by starting the proof below with
apply l
and notice that our assumptions don't change but the goal changes
from ⊢ U
to ⊢ T
.
Keep apply
ing functions until your goal is P
, and try not
to get lost! Now solve this goal
with exact p
. Note: you will need to learn the difference between
exact p
(which works) and exact P
(which doesn't, because P
is
not an element of P
).
Definition
Given an element of P
we can define an element of U
.
example (example: (P Q R S T U : Type) → P → (P → Q) → (Q → R) → (Q → T) → (S → T) → (T → U) → UPP: TypeQQ: TypeRR: TypeSS: TypeTT: TypeU:U: TypeType) (Type: Type 1p :p: PP) (P: Typeh :h: P → QP →P: TypeQ) (Q: Type:Q →Q: TypeR) (R: Typej :j: Q → TQ →Q: TypeT) (T: Type:S →S: TypeT) (T: Typel :l: T → UT →T: TypeU) :U: TypeU :=U: TypeP, Q, R, S, T, U: Type
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → UUP, Q, R, S, T, U: Type
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → UTP, Q, R, S, T, U: Type
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → UQP, Q, R, S, T, U: Type
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → UPGoals accomplished! 🐙
Next up Level 5
import MyNat.Definition
namespace MyNat
open MyNat
Function world.
Level 5: P → (Q → P)
.
In this level, our goal is to construct a function, like in level 2.
⊢ P → (Q → P)
So P
and Q
are sets, and our goal is to construct a function
which takes an element of P
and outputs a function from Q
to P
.
We don't know anything at all about the sets P
and Q
, so initially
this seems like a bit of a tall order. But let's give it a go. Delete
the sorry
and let's think about how to proceed.
Our goal is P → X
for some set X=Hom(Q,P)
, and if our
goal is to construct a function then we almost always want to use the
intro
tactic from level 2, Lean's version of "let p ∈ P
be arbitrary."
So let's start with
intro p
and we then find ourselves in this state:
P Q : Type,
p : P
⊢ Q → P
We now have an arbitrary element p ∈ P
and we are supposed to be constructing
a function Q → P
. Well, how about the constant function, which sends everything to p
?
This will work. So let q ∈ Q
be arbitrary:
intro q
and then let's output p
.
exact p
Definition
We define an element of Hom(P,Hom(Q,P))
.
example (example: (P Q : Type) → P → Q → PPP: TypeQ :Q: TypeType) :Type: Type 1P → (P: TypeQ →Q: TypeP) :=P: TypeP, Q: TypeP → Q → PP, Q: Type
p: PQ → PP, Q: Type
p: PQ → PP, Q: Type
p: P
q: QPGoals accomplished! 🐙
A mathematician would treat the set P → (Q → P)
as the same as the set P × Q → P
,
because to give an element of either function space is just to give a rule which takes
an element of P
and an element of Q
, and returns an element of P
. Thinking of the
goal as a function from P × Q
to P
we realize that it's just projection onto the first
factor.
Did you notice?
I wrote P → (Q → P)
but Lean just writes P → Q → P
. This is because
computer scientists adopt the convention that →
is right associative,
which is a fancy way of saying "when we write P → Q → R
, we mean P → (Q → R)
."
Mathematicians use right associativity as a convention for powers: if
a mathematician says \(10^{10^{10}}\) they don't mean \((10^{10})^{10}=10^{100}\), they
mean \(10^{(10^{10})}\). So 10 ^ 10 ^ 10
in Lean means 10 ^ (10 ^ 10)
and not (10 ^ 10) ^ 10
.
However they use left associativity as a convention for subtraction: if
a mathematician writes 6 - 2 - 1
they mean (6 - 2) - 1
and not 6 - (2 - 1)
.
Pro tip
intros p q
is the same as intro p; intro q
.
Next up Level 6
import MyNat.Definition
namespace MyNat
open MyNat
Function World
Level 6: (P → (Q → R)) → ((P → Q) → (P → R))
.
You can solve this level completely just using intro
, apply
and exact
,
but if you want to argue forwards instead of backwards then don't forget
that you can do things like
have j : Q → R := f p
if f : P → (Q → R)
and p : P
. Remember the trick with the colon in have
:
we could just write have j := f p
but this way we can be sure that j
is
what we actually expect it to be.
We start with intro f
rather than intro p
because even though the goal starts P → ...
, the brackets mean that
the goal is not a function from P
to anything, it's a function from
P → (Q → R)
to something. In fact you can save time by starting
with intros f h p
, which introduces three variables at once, although you'd
better then look at your tactic state to check that you called all those new
terms sensible things.
After all the intros, you find that the goal is ⊢ R
. If you try have j : Q → R := f p
now then you can apply j
. Alternatively you can apply (f p)
directly.
What happens if you just try apply f
? Can you figure out what just happened? This is a little
apply
easter egg. Why is it mathematically valid?
Definition
Whatever the sets P
and Q
and R
are, we
make an element of \(\operatorname{Hom}(\operatorname{Hom}(P,\operatorname{Hom}(Q,R)),
\operatorname{Hom}(\operatorname{Hom}(P,Q),\operatorname{Hom}(P,R)))\).
example (example: (P Q R : Type) → (P → Q → R) → (P → Q) → P → RPP: TypeQQ: TypeR :R: TypeType) : (Type: Type 1P → (P: TypeQ →Q: TypeR)) → ((R: TypeP →P: TypeQ) → (Q: TypeP →P: TypeR)) :=R: TypeP, Q, R: Type(P → Q → R) → (P → Q) → P → RP, Q, R: Type
f: P → Q → R(P → Q) → P → RP, Q, R: Type
f: P → Q → R
h: P → QP → RP, Q, R: Type
f: P → Q → R
h: P → Q
p: PRP, Q, R: Type
f: P → Q → R
h: P → Q
p: P
j: Q → RRP, Q, R: Type
f: P → Q → R
h: P → Q
p: P
j: Q → RQP, Q, R: Type
f: P → Q → R
h: P → Q
p: P
j: Q → RPGoals accomplished! 🐙
Next up Level 7
import MyNat.Definition
namespace MyNat
open MyNat
Function World
Level 7: (P → Q) → ((Q → F) → (P → F))
Have you noticed that, in stark contrast to earlier worlds, we are not amassing a large collection of useful theorems? We really are just constructing abstract levels with sets and functions, and then solving them and never using the results ever again. Here's another one, which should hopefully be very easy for you now. Advanced mathematician viewers will know it as contravariance of \(\operatorname{Hom}(\cdot,F)\) functor.
Definition
Whatever the sets P
and Q
and F
are, we
make an element of \(\operatorname{Hom}(\operatorname{Hom}(P,Q),
\operatorname{Hom}(\operatorname{Hom}(Q,F),\operatorname{Hom}(P,F)))\).
example (example: (P Q F : Type) → (P → Q) → (Q → F) → P → FPP: TypeQQ: TypeF :F: TypeType) : (Type: Type 1P →P: TypeQ) → ((Q: TypeQ →Q: TypeF) → (F: TypeP →P: TypeF)) :=F: TypeP, Q, F: Type(P → Q) → (Q → F) → P → FP, Q, F: Type
f: P → Q
h: Q → F
p: PFP, Q, F: Type
f: P → Q
h: Q → F
p: PQP, Q, F: Type
f: P → Q
h: Q → F
p: PPGoals accomplished! 🐙
Next up Level 8
import MyNat.Definition
namespace MyNat
open MyNat
Function world.
Level 8: (P → Q) → ((Q → empty) → (P → empty))
Level 8 is the same as level 7, except we have replaced the
set F
with the empty set ∅
. The same proof will work (after all, our
previous proof worked for all sets, and the empty set is a set).
But note that if you start with intro f; intro h; intro p,
(which can incidentally be shortened to intros f h p
,
see intros tactic),
then the local context looks like this:
P Q : Type,
f : P → Q,
h : Q → empty,
p : P
⊢ empty
and your job is to construct an element of the empty set!
This on the face of it seems hard, but what is going on is that
our hypotheses (we have an element of P
, and functions P → Q
and Q → ∅
) are themselves contradictory, so
I guess we are doing some kind of proof by contradiction at this point? However,
if your next line is apply h
then all of a sudden the goal
seems like it might be possible again. If this is confusing, note
that the proof of the previous world worked for all sets F
, so in particular
it worked for the empty set, you just probably weren't really thinking about
this case explicitly beforehand. [Technical note to constructivists: I know
that we are not doing a proof by contradiction. But how else do you explain
to a classical mathematician that their goal is to prove something false
and this is OK because their hypotheses don't add up?]
Definition
Whatever the sets P
and Q
are, we
make an element of \(\operatorname{Hom}(\operatorname{Hom}(P,Q),
\operatorname{Hom}(\operatorname{Hom}(Q,\emptyset),\operatorname{Hom}(P,\emptyset)))\).
example (example: {empty : Sort u_1} → (P Q : Type) → (P → Q) → (Q → empty) → P → emptyPP: TypeQ :Q: TypeType) : (Type: Type 1P →P: TypeQ) → ((Q: TypeQ →Q: Typeempty) → (empty: Sort u_1P →P: Typeempty)) :=empty: Sort u_1empty: Sort ?u.27
P, Q: Type(P → Q) → (Q → empty) → P → emptyempty: Sort ?u.27
P, Q: Type
f: P → Q
h: Q → empty
p: Pemptyempty: Sort ?u.27
P, Q: Type
f: P → Q
h: Q → empty
p: PQempty: Sort ?u.27
P, Q: Type
f: P → Q
h: Q → empty
p: PPGoals accomplished! 🐙
Next up Level 9
import MyNat.Definition
namespace MyNat
open MyNat
Function world.
Level 9: a big maze.
In Proposition World you will see a variant of this example which can be solved by a tactic. It would be an interesting project to make a tactic which could automatically solve this sort of level in Lean.
You can of course work both forwards and backwards, or you could crack and draw a picture.
Definition
Given a bunch of functions, we can define another one.
example (example: (A B C D E F G H I J K L : Type) → (A → B) → (B → E) → (E → D) → (D → A) → (E → F) → (F → C) → (B → C) → (F → G) → (G → J) → (I → J) → (J → I) → (I → H) → (E → H) → (H → K) → (I → L) → A → LAA: TypeBB: TypeCC: TypeDD: TypeEE: TypeFF: TypeGG: TypeHH: TypeII: TypeJJ: TypeKK: TypeL :L: TypeType) (Type: Type 1f1 :f1: A → BA →A: TypeB) (B: Typef2 :f2: B → EB →B: TypeE) (E: Type:E →E: TypeD) (D: Type:D →D: TypeA) (A: Typef5 :f5: E → FE →E: TypeF) (F: Type:F →F: TypeC) (C: Type:B →B: TypeC) (C: Typef8 :f8: F → GF →F: TypeG) (G: Typef9 :f9: G → JG →G: TypeJ) (J: Type:I →I: TypeJ) (J: Typef11 :f11: J → IJ →J: TypeI) (I: Type:I →I: TypeH) (H: Type:E →E: TypeH) (H: Type:H →H: TypeK) (K: Typef15 :f15: I → LI →I: TypeL) :L: TypeA →A: TypeL :=L: TypeA, B, C, D, E, F, G, H, I, J, K, L: Type
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → LA → LA, B, C, D, E, F, G, H, I, J, K, L: Type
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: ALA, B, C, D, E, F, G, H, I, J, K, L: Type
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: AIA, B, C, D, E, F, G, H, I, J, K, L: Type
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: AJA, B, C, D, E, F, G, H, I, J, K, L: Type
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: AGA, B, C, D, E, F, G, H, I, J, K, L: Type
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: AFA, B, C, D, E, F, G, H, I, J, K, L: Type
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: AEA, B, C, D, E, F, G, H, I, J, K, L: Type
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: ABA, B, C, D, E, F, G, H, I, J, K, L: Type
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: AAGoals accomplished! 🐙
Here we finish this proof with a new tactic assumption
instead of exact a
.
The assumption
tactic tries to solve the goal using a
hypothesis of compatible type. Since we have the hypothesis named a
it finds
it and completes the proof.
That's the end of Function World! Next it's Proposition World, and the tactics you've learnt in Function World are enough to solve all nine levels! In fact, the levels in Proposition World might look strangely familiar...
import PropositionWorld.Level1
import PropositionWorld.Level2
import PropositionWorld.Level3
import PropositionWorld.Level4
import PropositionWorld.Level5
import PropositionWorld.Level6
import PropositionWorld.Level7
import PropositionWorld.Level8
import PropositionWorld.Level9
Proposition world.
A Proposition is a true/false statement, like 2 + 2 = 4
or 2 + 2 = 5
.
Just like we can have concrete sets in Lean like MyNat
, and abstract
sets called things like X
, we can also have concrete propositions like
2 + 2 = 5
and abstract propositions called things like P
.
Mathematicians are very good at conflating a theorem with its proof.
They might say "now use theorem 12 and we're done". What they really
mean is "now use the proof of theorem 12..." (i.e. the fact that we proved
it already). Particularly problematic is the fact that mathematicians
use the word Proposition to mean "a relatively straightforward statement
which is true" and computer scientists use it to mean "a statement of
arbitrary complexity, which might be true or false". Computer scientists
are far more careful about distinguishing between a proposition and a proof.
For example: x + 0 = x
is a proposition, and add_zero x
is its proof. The convention we'll use is capital letters for propositions
and small letters for proofs.
In this world you will see the local context in the following kind of state:
P : Prop
p : P
Here P
is the true/false statement (the statement of proposition), and p
is its proof.
It's like P
being the set and p
being the element. In fact, computer scientists
sometimes think about the following analogy: propositions are like sets,
and their proofs are like their elements.
What's going on in this world?
We're going to learn about manipulating propositions and proofs.
Fortunately, we don't need to learn a bunch of new tactics -- the
ones we just learnt (exact
, intro
, have
, apply
) will be perfect.
The levels in proposition world are "back to normal", we're proving theorems, not constructing elements of sets. Or are we?
In Lean, Propositions, like sets, are types, and proofs, like elements of sets, are terms.
Let's get started Level 1
import MyNat.Definition
namespace MyNat
open MyNat
Proposition world.
Level 1: the exact
tactic.
The local context (or tactic state) at the beginning of the example below looks like this:
P Q : Prop,
p : P,
h : P → Q
⊢ Q
In this situation, we have true/false statements P
and Q
,
a proof p
of P
, and h
is the hypothesis that P → Q
(P implies Q).
Our goal is to construct a proof of Q
. It's clear what to do
mathematically to solve this goal, P
is true and P
implies Q
so Q
is true. But how to do it in Lean?
Adopting a point of view wholly unfamiliar to many mathematicians,
Lean interprets the hypothesis h
as a function from proofs
of P
to proofs of Q
, so the rather surprising approach
exact h p
works to close the goal!
Note that exact h P
(with a capital P) won't work;
this is a common error by beginners. "We're trying to solve P
so it's exactly P
". The goal states the theorem, your job is to
construct the proof. P
is not a proof of P
, it's p
that is a proof of P
.
The analogy would be like trying to call a function "sin X" with "Float" instead of a number 3.1415.
Lemma
If P
is true, and P → Q
is also true, then Q
is true.
example (example: ∀ (P Q : Prop), P → (P → Q) → QPP: PropQ :Q: PropProp) (Prop: Typep :p: PP) (P: Proph :h: P → QP →P: PropQ) :Q: PropQ :=Q: PropP, Q: Prop
p: P
h: P → QQGoals accomplished! 🐙
Look familiar? The reason this works so elegantly is because the Lean programming language has unified Propositions with the Type system of the language.
Next up Level 2
import MyNat.Definition
namespace MyNat
open MyNat
Proposition world.
Level 2: intro
.
Let's prove an implication. Let P
be a true/false statement, and let's prove that P → P
. You
will see that our goal in the lemma below starts out with P → P
. Constructing a term of type P → P
(which is what solving this goal means) in this case amounts to proving that P → P
, and
computer scientists think of this as coming up with a function which sends proofs of P
to proofs
of P
.
To define an implication P ⟹ Q
we need to choose an arbitrary proof p : P
of P
and then,
perhaps using p
, construct a proof of Q
. The Lean way to say "let's assume P
is true" is
intro p
, i.e., "let's assume we have a proof of P
".
Note for worriers.
Those of you who know something about the subtle differences between truth and provability discovered by Goedel -- these are not relevant here. Imagine we are working in a fixed model of mathematics, and when I say "proof" I actually mean "truth in the model", or "proof in the metatheory".
Rule of thumb:
If your goal is to prove P → Q
(i.e. that P → Q
)
then intro p
, meaning "assume p
is a proof of P
", will make progress.
To solve the goal below, you have to come up with a function from
P
(thought of as the set of proofs of P
!) to itself. Start with
intro p
(i.e. "let p
be a proof of P
") and note that our
local context now looks like this:
P : Prop,
p : P
⊢ P
Our job now is to construct a proof of P
. But p
is a proof of P
.
So
exact p
will close the goal. Note that exact P
will not work -- don't
confuse a true/false statement (which could be false!) with a proof.
We will stick with the convention of capital letters for propositions
and small letters for proofs.
Lemma : imp_self
If P
is a proposition then P → P
.
lemmaimp_self (imp_self: ∀ (P : Prop), P → PP :P: PropProp) :Prop: TypeP →P: PropP :=P: PropP: PropP → PP: Prop
p: PPGoals accomplished! 🐙
Next up Level 3
import MyNat.Definition
namespace MyNat
open MyNat
Proposition world.
Level 3: have
.
Say you have a whole bunch of propositions and implications between them, and your goal is to build
a certain proof of a certain proposition. If it helps, you can build intermediate proofs of other
propositions along the way, using the have
command. have q : Q := ...
is the Lean analogue of
saying "We now see that we can prove Q
, because..." in the middle of a proof. It is often not
logically necessary, but on the other hand it is very convenient, for example it can save on
notation, or it can break proofs up into smaller steps.
In the level below, we have a proof of P
and we want a proof of U
; during the proof we will
construct proofs of of some of the other propositions involved. The diagram of propositions and
implications looks like this pictorially:
and so it's clear how to deduce U
from P
.
Indeed, we could solve this level in one move by typing
exact l (j (h p))
But let us instead stroll more lazily through the level.
We can start by using the have
tactic to make a proof of Q
:
have q := h p
and then we note that j q
is a proof of T
:
have t : T := j q
(note how we explicitly told Lean what proposition we thought t
was
a proof of, with that : T
thing before the :=
)
and we could even define u
to be l t
:
have u : U := l t
and then finish the level with
exact u
Lemma : maze
In the maze of logical implications above, if P
is true then so is U
.
lemmamaze (maze: ∀ (P Q R S T U : Prop), P → (P → Q) → (Q → R) → (Q → T) → (S → T) → (T → U) → UPP: PropQQ: PropRR: PropSS: PropTT: PropU:U: PropProp) (Prop: Typep :p: PP) (P: Proph :h: P → QP →P: PropQ) (Q: Prop:Q →Q: PropR) (R: Propj :j: Q → TQ →Q: PropT) (T: Prop:S →S: PropT) (T: Propl :l: T → UT →T: PropU) :U: PropU :=U: PropP, Q, R, S, T, U: Prop
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → UUP, Q, R, S, T, U: Prop
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → U
q: QUP, Q, R, S, T, U: Prop
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → U
q: Q
t: TUP, Q, R, S, T, U: Prop
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → U
q: Q
t: T
u: UUGoals accomplished! 🐙
Remember you can move your cursor around with the arrow keys and explore the various tactic states
in this proof in Visual Studio Code, and note that the tactic state at the beginning of exact u
is
this mess:
P Q R S T U : Prop,
p : P,
h : P → Q,
i : Q → R,
j : Q → T,
k : S → T,
l : T → U,
q : Q,
t : T,
u : U
⊢ U
It was already bad enough to start with, and we added three more terms to it. In level 4 we will
learn about the apply
tactic which solves the level using another technique, without leaving so
much junk behind.
Next up Level 4
import MyNat.Definition
namespace MyNat
open MyNat
Proposition world.
Level 4: apply
.
Let's do the same level again a different way:
We are given a proof p
of P
and our goal is to find a proof of U
, or
in other words to find a path through the maze that links P
to U
.
In level 3 we solved this by using have
s to move forward, from P
to Q
to T
to U
. Using the apply
tactic we can instead construct
the path backwards, moving from U
to T
to Q
to P
.
Our goal is to prove U
. But l:T ⟹ U
is
an implication which we are assuming, so it would suffice to prove T
.
Tell Lean this by starting the proof below with
apply l,
and notice that our assumptions don't change but the goal changes
from ⊢ U
to ⊢ T
.
Keep apply
ing implications until your goal is P
, and try not
to get lost! Now solve this goal
with exact p
. Note: you will need to learn the difference between
exact p
(which works) and exact P
(which doesn't, because P
is
not a proof of P
).
Lemma : maze₂
We can solve a maze.
lemmamaze₂ (maze₂: ∀ (P Q R S T U : Prop), P → (P → Q) → (Q → R) → (Q → T) → (S → T) → (T → U) → UPP: PropQQ: PropRR: PropSS: PropTT: PropU:U: PropProp) (Prop: Typep :p: PP) (P: Proph :h: P → QP →P: PropQ) (Q: Prop:Q →Q: PropR) (R: Propj :j: Q → TQ →Q: PropT) (T: Prop:S →S: PropT) (T: Propl :l: T → UT →T: PropU) :U: PropU :=U: PropP, Q, R, S, T, U: Prop
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → UUP, Q, R, S, T, U: Prop
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → UTP, Q, R, S, T, U: Prop
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → UQP, Q, R, S, T, U: Prop
p: P
h: P → Q
i: Q → R
j: Q → T
k: S → T
l: T → UPGoals accomplished! 🐙
Next up Level 5
import MyNat.Definition
namespace MyNat
open MyNat
Proposition world.
Level 5 : P → (Q → P)
.
In this level, our goal is to construct an implication, like in level 2.
⊢ P → (Q → P)
So P
and Q
are propositions, and our goal is to prove
that P ⟹(Q ⟹ P)
.
We don't know whether P
, Q
are true or false, so initially
this seems like a bit of a tall order. But let's give it a go.
Our goal is P → X
for some true/false statement X
, and if our
goal is to construct an implication then we almost always want to use the
intro
tactic from level 2, Lean's version of "assume P
", or more precisely
"assume p
is a proof of P
". So let's start with
intro p
and we then find ourselves in this tactic state:
P Q : Prop,
p : P
⊢ Q → P
We now have a proof p
of P
and we are supposed to be constructing
a proof of Q ⟹ P
. So let's assume that Q
is true and try
and prove that P
is true. We assume Q
like this:
intro q
and now we have to prove P
, but have a proof handy:
exact p
Lemma
For any propositions P
and Q
, we always have
P ⟹(Q ⟹ P)
.
example (example: ∀ (P Q : Prop), P → Q → PPP: PropQ :Q: PropProp) :Prop: TypeP → (P: PropQ →Q: PropP) :=P: PropP, Q: PropP → Q → PP, Q: Prop
p: PQ → PP, Q: Prop
p: PQ → PP, Q: Prop
p: P
q: QPGoals accomplished! 🐙
A mathematician would treat the proposition P ⟹(Q ⟹ P)
as the same as the proposition P ∧ Q ⟹ P
,
because to give a proof of either of these is just to give a method which takes
a proof of P
and a proof of Q
, and returns a proof of P
. Thinking of the
goal as P ∧ Q ⟹ P
we see why it is provable.
Did you notice?
I wrote P → (Q → P)
but Lean just writes P → Q → P
. This is because
computer scientists adopt the convention that →
is right associative,
which is a fancy way of saying "when we write P → Q → R
, we mean P → (Q → R)
.
Mathematicians would never dream of writing something as ambiguous as
P ⟹ Q ⟹ R
(they are not really interested in proving abstract
propositions, they would rather work with concrete ones such as Fermat's Last Theorem),
so they do not have a convention for where the brackets go. It's important to
remember Lean's convention though, or else you will get confused. If your goal
is P → Q → R
then you need to know whether intro h
will create h : P
or h : P → Q
.
Make sure you understand which one.
Next up Level 6
import MyNat.Definition
namespace MyNat
open MyNat
Proposition world.
Level 6: (P → (Q → R)) → ((P → Q) → (P → R))
.
You can solve this level completely just using intro
, apply
and exact
,
but if you want to argue forwards instead of backwards then don't forget
that you can do things like have j : Q → R := f p
if f : P → (Q → R)
and p : P
. I recommend that you start with intro f
rather than intro p
because even though the goal starts P → ...
, the brackets mean that
the goal is not the statement that P
implies anything, it's the statement that
P ⟹ (Q ⟹ R)
implies something. In fact I'd recommend that you started
with intros f h p
, which introduces three variables at once.
You then find that your your goal is ⊢ R
. If you try have j : Q → R := f p
now then you can apply j
. Alternatively you can apply (f p)
directly.
What happens if you just try apply f
? Can you figure out what just happened? This is a little
apply
easter egg. Why is it mathematically valid?
Lemma
If P
and Q
and R
are true/false statements, then
\((P⟹(Q⟹ R))⟹((P⟹ Q)⟹(P⟹ R))\).
example (example: ∀ (P Q R : Prop), (P → Q → R) → (P → Q) → P → RPP: PropQQ: PropR :R: PropProp) : (Prop: TypeP → (P: PropQ →Q: PropR)) → ((R: PropP →P: PropQ) → (Q: PropP →P: PropR)) :=R: PropP, Q, R: Prop(P → Q → R) → (P → Q) → P → RP, Q, R: Prop
f: P → Q → R(P → Q) → P → RP, Q, R: Prop
f: P → Q → R
h: P → QP → RP, Q, R: Prop
f: P → Q → R
h: P → Q
p: PRP, Q, R: Prop
f: P → Q → R
h: P → Q
p: P
j: Q → RRP, Q, R: Prop
f: P → Q → R
h: P → Q
p: P
j: Q → RQP, Q, R: Prop
f: P → Q → R
h: P → Q
p: P
j: Q → RPGoals accomplished! 🐙
Next up Level 7
import MyNat.Definition
namespace MyNat
open MyNat
Function world.
Level 7: (P → Q) → ((Q → R) → (P → R))
If you start with intro hpq
and then intro hqr
the dust will clear a bit and the level will look like this:
P Q R : Prop,
hpq : P → Q,
hqr : Q → R
⊢ P → R
So this level is really about showing transitivity of ⟹
,
if you like that sort of language.
Lemma : imp_trans
From P ⟹ Q
and Q ⟹ R
we can deduce P ⟹ R
.
lemmaimp_trans (imp_trans: ∀ (P Q R : Prop), (P → Q) → (Q → R) → P → RPP: PropQQ: PropR :R: PropProp) : (Prop: TypeP →P: PropQ) → ((Q: PropQ →Q: PropR) → (R: PropP →P: PropR)) :=R: PropP, Q, R: Prop(P → Q) → (Q → R) → P → RP, Q, R: Prop
hpq: P → Q
hqr: Q → RP → RP, Q, R: Prop
hpq: P → Q
hqr: Q → R
p: PRP, Q, R: Prop
hpq: P → Q
hqr: Q → R
p: PQP, Q, R: Prop
hpq: P → Q
hqr: Q → R
p: PPGoals accomplished! 🐙
Here we finish this proof with a new tactic assumption
instead of exact p
.
The assumption
tactic tries to solve the goal using a
hypothesis of compatible type. Since we have the hypothesis named p
it finds
it and completes the proof.
Next up Level 8
import MyNat.Definition
namespace MyNat
open MyNat
Proposition world.
Level 8 : (P → Q) → (¬ Q → ¬ P)
There is a false proposition false
, with no proof. It is easy to check that ¬ Q
is equivalent to
Q ⟹ false
, and in this tutorial we call this
not_iff_imp_false (P : Prop) : ¬ P ↔ (P → false)
which we can prove here using the simp
tactic:
theoremnot_iff_imp_false (not_iff_imp_false: ∀ (P : Prop), ¬P ↔ P → false = trueP :P: PropProp) : ¬Prop: TypeP ↔ (P: PropP →P: Propfalse) :=false: BoolP: Prop¬P ↔ P → false = trueGoals accomplished! 🐙
So you can start the proof of the contrapositive below with
repeat rw [not_iff_imp_false]
to get rid of the two occurrences of ¬
, and I'm sure you can take it from there. At some point
your goal might be to prove false
. At that point I guess you must be proving something by
contradiction. Or are you?
Lemma : contrapositive
If P
and Q
are propositions, and P⟹ Q
, then
¬ Q⟹ ¬ P
.
lemmacontrapositive (contrapositive: ∀ (P Q : Prop), (P → Q) → ¬Q → ¬PPP: PropQ :Q: PropProp) : (Prop: TypeP →P: PropQ) → (¬Q: PropQ → ¬Q: PropP) :=P: PropP, Q: Prop(P → Q) → ¬Q → ¬PP, Q: Prop(P → Q) → ¬Q → ¬PP, Q: Prop(P → Q) → ¬Q → ¬PP, Q: Prop(P → Q) → (Q → false = true) → P → false = trueP, Q: Prop(P → Q) → (Q → false = true) → ¬PP, Q: Prop
f: P → Q(Q → false = true) → P → false = trueP, Q: Prop
f: P → Q
h: Q → false = trueP → false = trueP, Q: Prop
f: P → Q
h: Q → false = true
p: Pfalse = trueP, Q: Prop
f: P → Q
h: Q → false = true
p: PQP, Q: Prop
f: P → Q
h: Q → false = true
p: PPGoals accomplished! 🐙
Technical note
All of that rewriting you did with rw
in addition world was rewriting hypothesis of the form
h : X = Y
, but you can also rw [h]
if h : P ↔ Q
(because propositional extensionality says
that if P ⟺ Q
then P = Q
, and mathematicians use this whether or not they notice.)
Next up Level 9
import MyNat.Definition
namespace MyNat
open MyNat
Proposition world.
Level 9: a big maze.
Hint: Lean's "congruence closure" tactic cc
is good at mazes. You might want to try it now.
Perhaps I should have mentioned it earlier.
Lemma
There is a way through the following maze.
example (example: ∀ (A B C D E F G H I J K L : Prop), (A → B) → (B → E) → (E → D) → (D → A) → (E → F) → (F → C) → (B → C) → (F → G) → (G → J) → (I → J) → (J → I) → (I → H) → (E → H) → (H → K) → (I → L) → A → LAA: PropBB: PropCC: PropDD: PropEE: PropFF: PropGG: PropHH: PropII: PropJJ: PropKK: PropL :L: PropProp) (Prop: Typef1 :f1: A → BA →A: PropB) (B: Propf2 :f2: B → EB →B: PropE) (E: Prop:E →E: PropD) (D: Prop:D →D: PropA) (A: Propf5 :f5: E → FE →E: PropF) (F: Prop:F →F: PropC) (C: Prop:B →B: PropC) (C: Propf8 :f8: F → GF →F: PropG) (G: Propf9 :f9: G → JG →G: PropJ) (J: Prop:I →I: PropJ) (J: Propf11 :f11: J → IJ →J: PropI) (I: Prop:I →I: PropH) (H: Prop:E →E: PropH) (H: Prop:H →H: PropK) (K: Propf15 :f15: I → LI →I: PropL) :L: PropA →A: PropL :=L: PropA, B, C, D, E, F, G, H, I, J, K, L: Prop
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → LA → LA, B, C, D, E, F, G, H, I, J, K, L: Prop
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: ALA, B, C, D, E, F, G, H, I, J, K, L: Prop
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: AIA, B, C, D, E, F, G, H, I, J, K, L: Prop
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: AJA, B, C, D, E, F, G, H, I, J, K, L: Prop
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: AGA, B, C, D, E, F, G, H, I, J, K, L: Prop
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: AFA, B, C, D, E, F, G, H, I, J, K, L: Prop
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: AEA, B, C, D, E, F, G, H, I, J, K, L: Prop
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: ABA, B, C, D, E, F, G, H, I, J, K, L: Prop
f1: A → B
f2: B → E
f3: E → D
f4: D → A
f5: E → F
f6: F → C
f7: B → C
f8: F → G
f9: G → J
f10: I → J
f11: J → I
f12: I → H
f13: E → H
f14: H → K
f15: I → L
a: AA-- BUGBUG: NNG uses cc which is missing in lean4...Goals accomplished! 🐙
Now move onto advanced proposition world, where you will see
how to prove goals such as P ∧ Q
(P
and Q
), P ∨ Q
(P
or Q
),
P ↔ Q
(P ⟺ Q
).
You will need to learn five more tactics: split
, cases
,
left
, right
, and exfalso
,
but they are all straightforward, and furthermore they are
essentially the last tactics you
need to learn in order to complete all the levels of this tutorial,
including all the 17 levels of Inequality World.
Next up advanced proposition world.
import AdvancedPropositionWorld.Level1
import AdvancedPropositionWorld.Level2
import AdvancedPropositionWorld.Level3
import AdvancedPropositionWorld.Level4
import AdvancedPropositionWorld.Level5
import AdvancedPropositionWorld.Level6
import AdvancedPropositionWorld.Level7
import AdvancedPropositionWorld.Level8
import AdvancedPropositionWorld.Level9
import AdvancedPropositionWorld.Level10
Advanced proposition world.
In this world we will learn six key tactics needed to solve all the
levels of this world, namely constructor
, cases
, rcases
, left
, right
, and exfalso
.
These, and use
(which we'll get to in Inequality World) are all the
tactics you will need to understand all the levels of the tutorial.
Let's dive in: Level 1
import MyNat.Definition
namespace MyNat
open MyNat
Advanced Proposition world
Level 1: the constructor
tactic.
The logical symbol ∧
means "and". If P
and Q
are propositions, then P ∧ Q
is the proposition
"P
and Q
". If your goal is P ∧ Q
then you can make progress with the constructor
tactic.,
which turns one goal ⊢ P ∧ Q
into two goals, namely ⊢ P
and ⊢ Q
. In the level below, after a
constructor
, you can finish off the two new sub-goals with the exact
tactic since both p
and
q
provide exactly what we need. You could also use the assumption
tactic.
Lemma
If P
and Q
are true, then P ∧ Q
is true.
example (example: ∀ (P Q : Prop), P → Q → P ∧ QPP: PropQ :Q: PropProp) (Prop: Typep :p: PP) (P: Propq :q: QQ) :Q: PropP ∧P: PropQ :=Q: PropP, Q: Prop
p: P
q: QP ∧ QP, Q: Prop
p: P
q: Q
leftPP, Q: Prop
p: P
q: QQP, Q: Prop
p: P
q: Q
rightQGoals accomplished! 🐙
Next up Level 2
import MyNat.Definition
namespace MyNat
open MyNat
Advanced proposition world.
Level 2: the cases
tactic.
If P ∧ Q
is in the goal, then we can make progress with constructor
. But what if P ∧ Q
is a
hypothesis?
The lemma below asks us to prove P ∧ Q → Q ∧ P
, that is, symmetry of the "and" relation. The
obvious first move is
intro h
because the goal is an implication and this tactic is guaranteed to make progress. Now h : P ∧ Q
is a hypothesis, and we can extract the parts of this And.intro
using the cases
tactic
cases h with
This will give us two hypotheses p
and q
proving P
and Q
respectively. So we hold onto
these, the goal is now ⊢ Q ∧ P
which we can split using the constructor
tactic, then we can
easily pick off the two sub-goals ⊢ Q
and ⊢ P
using q
and p
respectively.
Lemma
If P
and Q
are true/false statements, then P ∧ Q ⟹ Q ∧ P
.
lemmaand_symm (and_symm: ∀ (P Q : Prop), P ∧ Q → Q ∧ PPP: PropQ :Q: PropProp) :Prop: TypeP ∧P: PropQ →Q: PropQ ∧Q: PropP :=P: PropP, Q: PropP ∧ Q → Q ∧ PP, Q: Prop
h: P ∧ QQ ∧ PP, Q: Prop
h: P ∧ QQ ∧ PP, Q: Prop
p: P
q: Q
introQ ∧ PP, Q: Prop
p: P
q: Q
intro.leftQP, Q: Prop
p: P
q: QPP, Q: Prop
p: P
q: Q
intro.rightPGoals accomplished! 🐙
Next up Level 3
import MyNat.Definition
namespace MyNat
open MyNat
Advanced proposition world.
Level 3: and_trans.
With this proof we can use the first cases
tactic to extract hypotheses p : P
q : Q
from
hpq : P ∧ Q
and then we can use another cases
tactic to extract hypotheses q' : Q
and r : R
from
hpr : Q ∧ R
then we can split the resulting goal ⊢ P ∧ R
using constructor
and easily pick off the
resulting sub-goals ⊢ P
and ⊢ R
using our given hypotheses.
Lemma
If P
, Q
and R
are true/false statements, then P ∧ Q
and
Q ∧ R
together imply P ∧ R
.
lemmaand_trans (and_trans: ∀ (P Q R : Prop), P ∧ Q → Q ∧ R → P ∧ RPP: PropQQ: PropR :R: PropProp) :Prop: TypeP ∧P: PropQ →Q: PropQ ∧Q: PropR →R: PropP ∧P: PropR :=R: PropP, Q, R: PropP ∧ Q → Q ∧ R → P ∧ RP, Q, R: Prop
hpq: P ∧ QQ ∧ R → P ∧ RP, Q, R: Prop
hpq: P ∧ Q
hqr: Q ∧ RP ∧ RP, Q, R: Prop
hpq: P ∧ Q
hqr: Q ∧ RP ∧ RP, Q, R: Prop
hqr: Q ∧ R
p: P
q: Q
introP ∧ RP, Q, R: Prop
hqr: Q ∧ R
p: P
q: Q
introP ∧ RP, Q, R: Prop
p: P
q, q': Q
r: R
intro.introP ∧ RP, Q, R: Prop
p: P
q, q': Q
r: R
intro.intro.leftPP, Q, R: Prop
p: P
q, q': Q
r: RRP, Q, R: Prop
p: P
q, q': Q
r: R
intro.intro.rightRGoals accomplished! 🐙
Next up Level 4
import MyNat.Definition
namespace MyNat
open MyNat
Advanced proposition world.
Level 4: iff_trans
.
The mathematical statement P ↔ Q
is equivalent to (P ⟹ Q) ∧ (Q ⟹ P)
. The cases
and split
tactics work on hypotheses and goals (respectively) of the form P ↔ Q
.
If you need to write an
↔
arrow in Visual Studio Code you can do so by typing\iff
. See the "Lean 4: Show All Abbreviations" command.
After an initial intro h
you can type cases h with hpq hqp
to break h : P ↔ Q
into its constituent parts.
Lemma
If P
, Q
and R
are true/false statements, then P ↔ Q
and Q ↔ R
together imply P ↔ R
.
lemmaiff_trans (iff_trans: ∀ (P Q R : Prop), (P ↔ Q) → (Q ↔ R) → (P ↔ R)PP: PropQQ: PropR :R: PropProp) : (Prop: TypeP ↔P: PropQ) → (Q: PropQ ↔Q: PropR) → (R: PropP ↔P: PropR) :=R: PropP, Q, R: Prop(P ↔ Q) → (Q ↔ R) → (P ↔ R)P, Q, R: Prop
hpq: P ↔ Q(Q ↔ R) → (P ↔ R)P, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ RP ↔ RP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ R
mpP → RP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ RR → PP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ R
mpP → RP, Q, R: Prop
hqr: Q ↔ R
pq: P → Q
qp: Q → P
mp.introP → RP, Q, R: Prop
hqr: Q ↔ R
pq: P → Q
qp: Q → P
mp.introP → RP, Q, R: Prop
pq: P → Q
qp: Q → P
qr: Q → R
rq: R → Q
mp.intro.introP → RP, Q, R: Prop
pq: P → Q
qp: Q → P
qr: Q → R
rq: R → Q
p: P
mp.intro.introRP, Q, R: Prop
pq: P → Q
qp: Q → P
qr: Q → R
rq: R → Q
p: P
mp.intro.introQP, Q, R: Prop
pq: P → Q
qp: Q → P
qr: Q → R
rq: R → Q
p: P
mp.intro.introPGoals accomplished! 🐙P, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ R
mprR → PP, Q, R: Prop
hqr: Q ↔ R
pq: P → Q
qp: Q → P
mpr.introR → PP, Q, R: Prop
hqr: Q ↔ R
pq: P → Q
qp: Q → P
mpr.introR → PP, Q, R: Prop
pq: P → Q
qp: Q → P
qr: Q → R
rq: R → Q
mpr.intro.introR → PP, Q, R: Prop
pq: P → Q
qp: Q → P
qr: Q → R
rq: R → Q
r: R
mpr.intro.introPP, Q, R: Prop
pq: P → Q
qp: Q → P
qr: Q → R
rq: R → Q
r: R
mpr.intro.introQP, Q, R: Prop
pq: P → Q
qp: Q → P
qr: Q → R
rq: R → Q
r: R
mpr.intro.introRGoals accomplished! 🐙
Next up Level 5
import MyNat.Definition
namespace MyNat
open MyNat
Advanced proposition world.
Level 5: iff_trans
easter eggs.
Let's try iff_trans
again. Try proving it in other ways.
A trick.
Instead of using cases
on h : P ↔ Q
you can just access the proofs of P → Q
and Q → P
directly with h.mp
and h.mpr
.
Lemma
If P
, Q
and R
are true/false statements, then P ↔ Q
and Q ↔ R
together imply P ↔ R
.
lemmaiff_trans₂ (iff_trans₂: ∀ (P Q R : Prop), (P ↔ Q) → (Q ↔ R) → (P ↔ R)PP: PropQQ: PropR :R: PropProp) : (Prop: TypeP ↔P: PropQ) → (Q: PropQ ↔Q: PropR) → (R: PropP ↔P: PropR) :=R: PropP, Q, R: Prop(P ↔ Q) → (Q ↔ R) → (P ↔ R)P, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ RP ↔ RP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ R
mpP → RP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ RR → PP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ R
p: P
mpRP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ RR → PP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ R
p: P
mpQP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ RR → PP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ R
p: P
mpPP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ RR → PP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ R
mprR → PP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ R
r: R
mprPP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ R
r: R
mprQP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ R
r: R
mprRGoals accomplished! 🐙
Another trick
Instead of using cases
on h : P ↔ Q
, you can just rw [h]
, and this will change all P
s to Q
s
in the goal. You can use this to create a much shorter proof. Note that
this is an argument for not running the cases
tactic on an iff statement;
you cannot rewrite one-way implications, but you can rewrite two-way implications.
lemmaiff_trans₃ (iff_trans₃: ∀ (P Q R : Prop), (P ↔ Q) → (Q ↔ R) → (P ↔ R)PP: PropQQ: PropR :R: PropProp) : (Prop: TypeP ↔P: PropQ) → (Q: PropQ ↔Q: PropR) → (R: PropP ↔P: PropR) :=R: PropP, Q, R: Prop(P ↔ Q) → (Q ↔ R) → (P ↔ R)P, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ RP ↔ RP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ RP ↔ RP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ RQ ↔ RP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ RQ ↔ RP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ RQ ↔ RP, Q, R: Prop
hpq: P ↔ Q
hqr: Q ↔ RR ↔ RGoals accomplished! 🐙
Next up Level 6
import Mathlib.Tactic.LeftRight
Advanced proposition world.
Level 6: Or, and the left
and right
tactics.
P ∨ Q
means "P
or Q
". So to prove it, you need to choose one of P
or Q
, and prove that
one. If ⊢ P ∨ Q
is your goal, then left
changes this goal to ⊢ P
, and right
changes it to ⊢ Q
.
Note that you can take a wrong turn here. Let's start with trying to prove Q ⟹ (P ∨ Q)
. After
the intro
, one of left
and right
leads to an impossible goal, the other to an easy finish.
Lemma
If P
and Q
are true/false statements, then Q ⟹(P ∨ Q).
example (example: ∀ (P Q : Prop), Q → P ∨ QPP: PropQ :Q: PropProp) :Prop: TypeQ → (Q: PropP ∨P: PropQ) :=Q: PropP, Q: PropQ → P ∨ QP, Q: Prop
q: QP ∨ QP, Q: Prop
q: Q
hQGoals accomplished! 🐙
Details
The tactics left
and right
work on a goal which is a type with
two constructors, the classic example being P ∨ Q
.
To prove P ∨ Q
it suffices to either prove P
or prove Q
,
and once you know which one you are going for you can change
the goal with left
or right
to the appropriate choice.
Pro Tip!
Did you spot the import Mathlib.Tactic.LeftRight
? What do you think it does?
You can make Mathlib available to your Lean package by adding the following
to your lakefile.lean
:
require mathlib from git
"https://github.com/leanprover-community/mathlib4.git" @ "56b19bdec560037016e326795d0feaa23b402c20"
This specifies a precise version of mathlib4 by commit Id.
Next up Level 7
import Mathlib.Tactic.LeftRight
import Mathlib.Tactic.Basic
import Mathlib.Tactic.Cases
Advanced proposition world.
Level 7: or_symm
Proving that (P ∨ Q) ⟹ (Q ∨ P)
involves an element of danger. intro h
is the obvious start. But
now, even though the goal is an ∨
statement, both left
and right
put you in a situation with
an impossible goal. Fortunately, after intro h
you can do cases h with
. Then something new
happens: because there are two ways to prove P ∨ Q
(namely, proving P
or proving Q
), the
cases
tactic turns one goal into two, one for each case. Each branch is easy to solve
using the left
and right
tactics we used in Level 6.
Lemma
If P
and Q
are true/false statements, then P ∨ Q ⟹ Q ∨ P.
lemmaor_symm (or_symm: ∀ (P Q : Prop), P ∨ Q → Q ∨ PPP: PropQ :Q: PropProp) :Prop: TypeP ∨P: PropQ →Q: PropQ ∨Q: PropP :=P: PropP, Q: PropP ∨ Q → Q ∨ PP, Q: Prop
h: P ∨ QQ ∨ PP, Q: Prop
h: P ∨ QQ ∨ PP, Q: Prop
hp: P
inlQ ∨ PP, Q: Prop
hp: P
inl.hPGoals accomplished! 🐙P, Q: Prop
hq: Q
inrQ ∨ PP, Q: Prop
hq: Q
inr.hQGoals accomplished! 🐙
Next up Level 8
import Mathlib.Tactic.LeftRight
import Mathlib.Tactic.Basic
import Std.Tactic.RCases
Advanced proposition world.
Level 8: and_or_distrib_left
We know that x(y+z)=xy+xz
for numbers, and this is called distributivity of multiplication over
addition. The same is true for ∧
and ∨
-- in fact ∧
distributes over ∨
and ∨
distributes
over ∧
. Let's prove one of these.
Some new tactics are handy here, the rintro
tactic is a combination of the intros
tactic with
rcases
to allow for destructuring patterns while introducing variables. For example,
rintro ⟨HP, HQ | HR⟩
below matches the subgoal P ∧ (Q ∨ R)
and introduces the new hypothesis
HP : P
and breaks the Or Q ∨ R
into two left and right sub-goals each with
hypothesis HQ : Q
and HR : R
.
Notice here that you can use a semi-colon to separate multiple tactics on the same line. Another
trick shown below is the <;> tactic. We could have written left; constructor; assumption; assumption
since the constructor
produces two sub-goals we need 2 assumption
tactics to close those, or you
can just write <;> assumption
which runs assumption
on both sub-goals.
Lemma
If P
. Q
and R
are true/false statements, then
P ∧ (Q ∨ R) ↔ (P ∧ Q) ∨ (P ∧ R).
lemmaand_or_distrib_left (and_or_distrib_left: ∀ (P Q R : Prop), P ∧ (Q ∨ R) ↔ P ∧ Q ∨ P ∧ RPP: PropQQ: PropR :R: PropProp) :Prop: TypeP ∧ (P: PropQ ∨Q: PropR) ↔ (R: PropP ∧P: PropQ) ∨ (Q: PropP ∧P: PropR) :=R: PropP, Q, R: PropP ∧ (Q ∨ R) ↔ P ∧ Q ∨ P ∧ RP, Q, R: Prop
mpP ∧ (Q ∨ R) → P ∧ Q ∨ P ∧ RP, Q, R: PropP ∧ Q ∨ P ∧ R → P ∧ (Q ∨ R)P, Q, R: Prop
mpP ∧ (Q ∨ R) → P ∧ Q ∨ P ∧ RP, Q, R: Prop
HP: P
HQ: Q
mp.intro.inlP ∧ Q ∨ P ∧ RP, Q, R: Prop
HP: P
HR: RP ∧ Q ∨ P ∧ R;P, Q, R: Prop
HP: P
HQ: Q
mp.intro.inl.hP ∧ QP, Q, R: Prop
HP: P
HR: RP ∧ Q ∨ P ∧ RP, Q, R: Prop
HP: P
HQ: Q
mp.intro.inl.h.leftPP, Q, R: Prop
HP: P
HQ: QQP, Q, R: Prop
HP: P
HQ: Q
mp.intro.inl.h.leftPP, Q, R: Prop
HP: P
HQ: QQ;Goals accomplished! 🐙;P, Q, R: Prop
HP: P
HR: R
mp.intro.inr.hP ∧ RP, Q, R: Prop
HP: P
HR: R
mp.intro.inr.h.leftPP, Q, R: Prop
HP: P
HR: RRP, Q, R: Prop
HP: P
HR: R
mp.intro.inr.h.leftPP, Q, R: Prop
HP: P
HR: RRGoals accomplished! 🐙P, Q, R: Prop
mprP ∧ Q ∨ P ∧ R → P ∧ (Q ∨ R)P, Q, R: Prop
mprP ∧ Q ∨ P ∧ R → P ∧ (Q ∨ R)P, Q, R: Prop
HP: P
HQ: Q
mpr.inl.introP ∧ (Q ∨ R)P, Q, R: Prop
HP: P
HR: RP ∧ (Q ∨ R);P, Q, R: Prop
HP: P
HQ: Q
mpr.inl.intro.leftPP, Q, R: Prop
HP: P
HQ: QQ ∨ RP, Q, R: Prop
HP: P
HR: RP ∧ (Q ∨ R);P, Q, R: Prop
HP: P
HQ: Q
mpr.inl.intro.rightQ ∨ RP, Q, R: Prop
HP: P
HR: RP ∧ (Q ∨ R);P, Q, R: Prop
HP: P
HQ: Q
mpr.inl.intro.right.hQP, Q, R: Prop
HP: P
HR: RP ∧ (Q ∨ R)P, Q, R: Prop
HP: P
HR: R
mpr.inr.introP ∧ (Q ∨ R);P, Q, R: Prop
HP: P
HR: R
mpr.inr.intro.leftPP, Q, R: Prop
HP: P
HR: RQ ∨ R;P, Q, R: Prop
HP: P
HR: R
mpr.inr.intro.rightQ ∨ R;P, Q, R: Prop
HP: P
HR: R
mpr.inr.intro.right.hRGoals accomplished! 🐙Goals accomplished! 🐙
Pro tip
Notice here we have used curly braces to group the answers to each of the two sub-goals produced by
the constructor
tactic. But you if you don't like curly braces you can also use dots like this:
lemmaand_or_distrib_left₂ (and_or_distrib_left₂: ∀ (P Q R : Prop), P ∧ (Q ∨ R) ↔ P ∧ Q ∨ P ∧ RPP: PropQQ: PropR :R: PropProp) :Prop: TypeP ∧ (P: PropQ ∨Q: PropR) ↔ (R: PropP ∧P: PropQ) ∨ (Q: PropP ∧P: PropR) :=R: PropP, Q, R: PropP ∧ (Q ∨ R) ↔ P ∧ Q ∨ P ∧ RP, Q, R: Prop
mpP ∧ (Q ∨ R) → P ∧ Q ∨ P ∧ RP, Q, R: PropP ∧ Q ∨ P ∧ R → P ∧ (Q ∨ R)P, Q, R: Prop
mpP ∧ (Q ∨ R) → P ∧ Q ∨ P ∧ RP, Q, R: Prop
HP: P
HQ: Q
mp.intro.inlP ∧ Q ∨ P ∧ RP, Q, R: Prop
HP: P
HR: RP ∧ Q ∨ P ∧ R;P, Q, R: Prop
HP: P
HQ: Q
mp.intro.inl.hP ∧ QP, Q, R: Prop
HP: P
HR: RP ∧ Q ∨ P ∧ RP, Q, R: Prop
HP: P
HQ: Q
mp.intro.inl.h.leftPP, Q, R: Prop
HP: P
HQ: QQP, Q, R: Prop
HP: P
HQ: Q
mp.intro.inl.h.leftPP, Q, R: Prop
HP: P
HQ: QQGoals accomplished! 🐙;P, Q, R: Prop
HP: P
HR: R
mp.intro.inr.hP ∧ RP, Q, R: Prop
HP: P
HR: R
mp.intro.inr.h.leftPP, Q, R: Prop
HP: P
HR: RRP, Q, R: Prop
HP: P
HR: R
mp.intro.inr.h.leftPP, Q, R: Prop
HP: P
HR: RRGoals accomplished! 🐙P, Q, R: Prop
mprP ∧ Q ∨ P ∧ R → P ∧ (Q ∨ R)P, Q, R: Prop
HP: P
HQ: Q
mpr.inl.introP ∧ (Q ∨ R)P, Q, R: Prop
HP: P
HR: RP ∧ (Q ∨ R);P, Q, R: Prop
HP: P
HQ: Q
mpr.inl.intro.leftPP, Q, R: Prop
HP: P
HQ: QQ ∨ RP, Q, R: Prop
HP: P
HR: RP ∧ (Q ∨ R);P, Q, R: Prop
HP: P
HQ: Q
mpr.inl.intro.rightQ ∨ RP, Q, R: Prop
HP: P
HR: RP ∧ (Q ∨ R);P, Q, R: Prop
HP: P
HQ: Q
mpr.inl.intro.right.hQP, Q, R: Prop
HP: P
HR: RP ∧ (Q ∨ R)P, Q, R: Prop
HP: P
HR: R
mpr.inr.introP ∧ (Q ∨ R);P, Q, R: Prop
HP: P
HR: R
mpr.inr.intro.leftPP, Q, R: Prop
HP: P
HR: RQ ∨ R;P, Q, R: Prop
HP: P
HR: R
mpr.inr.intro.rightQ ∨ R;P, Q, R: Prop
HP: P
HR: R
mpr.inr.intro.right.hRGoals accomplished! 🐙
Where the definition of the dot is:
Given a goal state [g1, g2, ... gn], . tacs is a tactic which first changes the goal state to [g1], then runs tacs. If the resulting goal state is not [], throw an error. Then restore the remaining goals [g2, ..., gn].
Next up Level 9
import Mathlib.Tactic.LeftRight
import Mathlib.Tactic.Basic
import Lean.Meta.Tactic.Apply
import Lean.Meta.Tactic.Cases
import PropositionWorld.Level8 -- not_iff_imp_false
Advanced proposition world.
You already know enough to embark on advanced addition world. But here are just a couple more things.
Level 9: exfalso
and proof by contradiction.
It's certainly true that P ∧ (¬ P) ⟹ Q
for any propositions P
and Q
, because the left hand side of the implication is false. But how do
we prove that false
implies any proposition Q
? A cheap way of doing it in
Lean is using the exfalso
tactic, which changes any goal at all to false
.
You might think this is a step backwards, but if you have a hypothesis h : ¬ P
then after rw not_iff_imp_false at h,
you can apply h,
to make progress.
Lemma
If P
and Q
are true/false statements, then (P ∧ (¬ P)) ⟹ Q.
lemmacontra (contra: ∀ (P Q : Prop), P ∧ ¬P → QPP: PropQ :Q: PropProp) : (Prop: TypeP ∧ ¬P: PropP) →P: PropQ :=Q: PropP, Q: PropP ∧ ¬P → QP, Q: Prop
h: P ∧ ¬PQP, Q: Prop
h: P ∧ ¬PQP, Q: Prop
p: P
np: ¬P
introQP, Q: Prop
p: P
np: ¬P
intro.hFalseP, Q: Prop
p: P
np: ¬P
intro.hPGoals accomplished! 🐙
Pro tip.
¬ P
is actually P → false
by definition and since np: ¬ P
is a hypothesis,
apply q
changes ⊢ False
to ⊢ P
. Neat trick. We started with ⊢ Q
, but
could not prove it so we jumped to False
so we could use np: ¬ P
to get to
the desired goal ⊢ P
.
Next up Level 10
import MyNat.Definition
namespace MyNat
open MyNat
Advanced proposition world.
Level 10: the law of the excluded middle.
We proved earlier that (P → Q) → (¬ Q → ¬ P)
. The converse,
that (¬ Q → ¬ P) → (P → Q)
is certainly true, but trying to prove
it using what we've learnt so far is impossible (because it is not provable in
constructive logic). For example, after
intro h
intro p
repeat rw [not_iff_imp_false] at h
in the below, you are left with
P Q : Prop,
h : (Q → false) → P → false
p : P
⊢ Q
The tools you have are not sufficient to continue. But you can just prove this, and any other basic
lemmas of this form like ¬ ¬ P → P
, using the by_cases
tactic. Here we start with the usual
intros
to turn the implication into hypotheses h : ¬ Q → ¬ P
and p : P
which leaves with the
goal of ⊢ Q
. But how can you prove Q
using these hypotheses? You can use this tactic:
by_cases q : Q
This creates two sub-goals pos
and neg
with the first one assuming Q is true - which can easily
satisfy the goal! and the second one assuming Q is false. But how can h: ¬Q → ¬P
, p: P
, q: ¬Q
prove the goal ⊢ Q
? Well if you apply q
to the hypothesis h
you end up with the conclusion
¬ P
, but then you have a contradiction in your hypotheses saying P
and ¬ P
which the
contradiction
tactic can take care of.
The contradiction
tactic closes the main goal if its hypotheses
are "trivially contradictory".
Lemma
If P
and Q
are true/false statements, then
(¬ Q ⟹ ¬ P) ⟹ (P ⟹ Q).
lemmacontrapositive2 (contrapositive2: ∀ (P Q : Prop), (¬Q → ¬P) → P → QPP: PropQ :Q: PropProp) : (¬Prop: TypeQ → ¬Q: PropP) → (P: PropP →P: PropQ) :=Q: PropP, Q: Prop(¬Q → ¬P) → P → QP, Q: Prop
h: ¬Q → ¬PP → QP, Q: Prop
h: ¬Q → ¬P
p: PQP, Q: Prop
h: ¬Q → ¬P
p: P
q: Q
posQP, Q: Prop
h: ¬Q → ¬P
p: P
q: ¬QQP, Q: Prop
h: ¬Q → ¬P
p: P
q: ¬Q
negQP, Q: Prop
h: ¬Q → ¬P
p: P
q: ¬Q
np: ¬P
negQGoals accomplished! 🐙
OK that's enough logic -- now perhaps it's time to go on to Advanced Addition World!
Pro tip
In fact the tactic tauto!
just kills this goal (and many other logic goals) immediately.
Each of these can now be proved using intro
, apply
, exact
and exfalso
.
Remember though that in these simple logic cases, high-powered logic
tactics like tauto!
will just prove everything.
import AdvancedAdditionWorld.Level1
import AdvancedAdditionWorld.Level2
import AdvancedAdditionWorld.Level3
import AdvancedAdditionWorld.Level4
import AdvancedAdditionWorld.Level5
import AdvancedAdditionWorld.Level6
import AdvancedAdditionWorld.Level7
import AdvancedAdditionWorld.Level8
import AdvancedAdditionWorld.Level9
import AdvancedAdditionWorld.Level10
import AdvancedAdditionWorld.Level11
import AdvancedAdditionWorld.Level12
import AdvancedAdditionWorld.Level13
Advanced Addition World.
Peano's original collection of axioms for the natural numbers contained two further assumptions, which have not yet been mentioned in the tutorial:
succ_inj {a b : MyNat} :
succ a = succ b → a = b
zero_ne_succ (a : MyNat) :
zero ≠ succ a
The reason they have not been used yet is that they are both implications, that is, of the form P ⟹ Q
.
This is clear for succ_inj a b
, which says that for all a
and b
we have succ a = succ b ⟹ a = b
.
For zero_ne_succ
the trick is that X ≠ Y
is defined to mean X = Y ⟹ false
. If you have
understood through Proposition world, you now have the required Lean
skills (i.e., you know the required tactics) to work with these implications.
Let's dive in: Level 1
import MyNat.Definition
import MyNat.Addition
import AdditionWorld.Level6
namespace MyNat
open MyNat
axiom succ_inj: ∀ {a b : MyNat}, succ a = succ b → a = b
succ_inj {a: MyNat
a b: MyNat
b : MyNat: Type
MyNat} : succ: MyNat → MyNat
succ a: MyNat
a = succ: MyNat → MyNat
succ b: MyNat
b → a: MyNat
a = b: MyNat
b
Advanced Addition World
Level 1: succ_inj
. A function.
Let's learn how to use succ_inj
. You should know a couple of ways to prove the below -- one
directly using an exact
, and one which uses an apply
first. But either way you'll need to use
succ_inj
.
Theorem
For all naturals a
and b
, if we assume succ a = succ b
, then we can
deduce a = b
.
theoremsucc_inj' {succ_inj': ∀ {a b : MyNat}, succ a = succ b → a = baa: MyNatb :b: MyNatMyNat} (MyNat: Typehs :hs: succ a = succ bsuccsucc: MyNat → MyNata =a: MyNatsuccsucc: MyNat → MyNatb) :b: MyNata =a: MyNatb :=b: MyNata, b: MyNat
hs: succ a = succ ba = bGoals accomplished! 🐙
Important thing.
You can rewrite proofs of equalities. If h : A = B
then rw [h]
changes A
s to B
s.
But you cannot rewrite proofs of implications. rw [succ_inj]
will never work
because succ_inj
isn't of the form A = B
, it's of the form A⟹ B
. This is one
of the most common mistakes I see from beginners. ⟹
and =
are two different things
and you need to be clear about which one you are using.
Next up Level 2
import MyNat.Definition
import MyNat.Addition
import AdvancedAdditionWorld.Level1 -- succ_inj
namespace MyNat
open MyNat
Advanced Addition World
Level 2: succ_succ_inj
In the below theorem, we need to apply succ_inj
twice. Once to prove
succ (succ a) = succ (succ b) ⟹ succ a = succ b
, and then again to prove succ a = succ b ⟹ a = b
.
However succ a = succ b
is nowhere to be found, it's neither an assumption or a goal when we start
this level. You can make it with have
or you can use apply
.
Theorem : succ_succ_inj
For all naturals a
and b
, if we assume succ (succ a) = succ (succ b)
, then we can
deduce a = b
.
theoremsucc_succ_inj {succ_succ_inj: ∀ {a b : MyNat}, succ (succ a) = succ (succ b) → a = baa: MyNatb :b: MyNatMyNat} (MyNat: Typeh :h: succ (succ a) = succ (succ b)succ (succ: MyNat → MyNatsuccsucc: MyNat → MyNata) =a: MyNatsucc (succ: MyNat → MyNatsuccsucc: MyNat → MyNatb)) :b: MyNata =a: MyNatb :=b: MyNata, b: MyNat
h: succ (succ a) = succ (succ b)a = ba, b: MyNat
h: succ (succ a) = succ (succ b)
asucc a = succ ba, b: MyNat
h: succ (succ a) = succ (succ b)
a.asucc (succ a) = succ (succ b)Goals accomplished! 🐙
Other solutions
Make sure you understand them all. And remember that rw
should not be used
with succ_inj
-- rw
works only with equalities or ↔
statements,
not implications or functions.
example {example: ∀ {a b : MyNat}, succ (succ a) = succ (succ b) → a = baa: MyNatb :b: MyNatMyNat} (MyNat: Typeh :h: succ (succ a) = succ (succ b)succ (succ: MyNat → MyNatsuccsucc: MyNat → MyNata) =a: MyNatsucc (succ: MyNat → MyNatsuccsucc: MyNat → MyNatb)) :b: MyNata =a: MyNatb :=b: MyNata, b: MyNat
h: succ (succ a) = succ (succ b)a = ba, b: MyNat
h: succ (succ a) = succ (succ b)
asucc a = succ bGoals accomplished! 🐙example {example: ∀ {a b : MyNat}, succ (succ a) = succ (succ b) → a = baa: MyNatb :b: MyNatMyNat} (MyNat: Typeh :h: succ (succ a) = succ (succ b)succ (succ: MyNat → MyNatsuccsucc: MyNat → MyNata) =a: MyNatsucc (succ: MyNat → MyNatsuccsucc: MyNat → MyNatb)) :b: MyNata =a: MyNatb :=b: MyNata, b: MyNat
h: succ (succ a) = succ (succ b)a = bGoals accomplished! 🐙
Next up Level 3
import MyNat.Definition
import MyNat.Addition
namespace MyNat
open MyNat
Advanced Addition World
Level 3: succ_eq_succ_of_eq
.
We are going to prove something completely obvious: if a=b
then
succ a = succ b
. This is not succ_inj
!
This is trivial -- we can just rewrite our proof of a=b
.
But how do we get to that proof? Use the intro
tactic.
Theorem
For all naturals a
and b
, a = b ⟹ succ a = succ b
.
theoremsucc_eq_succ_of_eq {succ_eq_succ_of_eq: ∀ {a b : MyNat}, a = b → succ a = succ baa: MyNatb :b: MyNatMyNat} :MyNat: Typea =a: MyNatb →b: MyNatsuccsucc: MyNat → MyNata =a: MyNatsuccsucc: MyNat → MyNatb :=b: MyNata, b: MyNata = b → succ a = succ ba, b: MyNat
h: a = bsucc a = succ ba, b: MyNat
h: a = bsucc a = succ ba, b: MyNat
h: a = bsucc b = succ bGoals accomplished! 🐙
Next up Level 4
import MyNat.Definition
import MyNat.Addition
import AdditionWorld.Level6
import AdvancedAdditionWorld.Level1 -- succ_inj
import AdvancedAdditionWorld.Level3 -- succ_eq_succ_of_eq
namespace MyNat
open MyNat
Advanced Addition World
Level 4: eq_iff_succ_eq_succ
Here is an iff
goal. You can split it into two goals (the implications in both
directions) using the constructor
tactic, which is how you're going to have to start.
constructor
Now you have two goals. The first is exactly succ_inj
so you can close
it with
exact succ_inj
and the second one you could solve by looking up the name of the theorem
you proved in the last level and doing exact <that name>
, or alternatively
you could get some more intro
practice and seeing if you can prove it
using intro
and rw
.
Remember that succ_inj
is succ a = succ b → a = b
.
Theorem
Two natural numbers are equal if and only if their successors are equal.
theoremsucc_eq_succ_iff (succ_eq_succ_iff: ∀ (a b : MyNat), succ a = succ b ↔ a = baa: MyNatb :b: MyNatMyNat) :MyNat: Typesuccsucc: MyNat → MyNata =a: MyNatsuccsucc: MyNat → MyNatb ↔b: MyNata =a: MyNatb :=b: MyNata, b: MyNatsucc a = succ b ↔ a = ba, b: MyNat
mpsucc a = succ b → a = ba, b: MyNata = b → succ a = succ ba, b: MyNat
mpra = b → succ a = succ bGoals accomplished! 🐙
Next up Level 5
import MyNat.Definition
import MyNat.Addition -- add_zero
import AdvancedAdditionWorld.Level1 -- succ_inj
namespace MyNat
open MyNat
Advanced Addition World
Level 5: add_right_cancel
The theorem add_right_cancel
is the theorem that you can cancel on the right
when you're doing addition -- if a + t = b + t
then a = b
. After intro h
I'd recommend induction on t
. Don't forget that rw [add_zero] at h
can be used
to do rewriting of hypotheses rather than the goal.
Theorem
On the set of natural numbers, addition has the right cancellation property.
In other words, if there are natural numbers a, b
and c
such that
a + t = b + t
then we have a = b
.
theoremadd_right_cancel (add_right_cancel: ∀ (a t b : MyNat), a + t = b + t → a = baa: MyNattt: MyNatb :b: MyNatMyNat) :MyNat: Typea +a: MyNatt =t: MyNatb +b: MyNatt →t: MyNata =a: MyNatb :=b: MyNata, t, b: MyNata + t = b + t → a = ba, t, b: MyNat
h: a + t = b + ta = ba, t, b: MyNat
h: a + t = b + ta = ba, b: MyNat
h: a + zero = b + zero
zeroa = ba, b: MyNat
h: a + zero = b + zero
zeroa = ba, b: MyNat
h: a + 0 = b + 0
zeroa = ba, b: MyNat
h: a + 0 = b + 0
zeroa = ba, b: MyNat
h: a + 0 = b + 0
zeroa = ba, b: MyNat
h: a + 0 = b + 0
zeroa = ba, b: MyNat
h: a = b + 0
zeroa = ba, b: MyNat
h: a = b + 0
zeroa = ba, b: MyNat
h: a = b + 0
zeroa = ba, b: MyNat
h: a = b + 0
zeroa = ba, b: MyNat
h: a = b
zeroa = ba, b: MyNat
h: a = b
zeroa = ba, b: MyNat
h: a = b
zeroa = bGoals accomplished! 🐙a, b, d: MyNat
ih: a + d = b + d → a = b
h: a + succ d = b + succ d
succa = ba, b, d: MyNat
ih: a + d = b + d → a = b
h: a + succ d = b + succ d
succa + d = b + da, b, d: MyNat
ih: a + d = b + d → a = b
h: a + succ d = b + succ d
succa + d = b + da, b, d: MyNat
ih: a + d = b + d → a = b
h: succ (a + d) = b + succ d
succa + d = b + da, b, d: MyNat
ih: a + d = b + d → a = b
h: succ (a + d) = b + succ d
succa + d = b + da, b, d: MyNat
ih: a + d = b + d → a = b
h: succ (a + d) = b + succ d
succa + d = b + da, b, d: MyNat
ih: a + d = b + d → a = b
h: succ (a + d) = b + succ d
succa + d = b + da, b, d: MyNat
ih: a + d = b + d → a = b
h: succ (a + d) = succ (b + d)
succa + d = b + da, b, d: MyNat
ih: a + d = b + d → a = b
h: succ (a + d) = succ (b + d)
succa + d = b + da, b, d: MyNat
ih: a + d = b + d → a = b
h: succ (a + d) = succ (b + d)
succa + d = b + dGoals accomplished! 🐙
Next up Level 6
import MyNat.Definition
import AdditionWorld.Level4 -- add_comm
import AdvancedAdditionWorld.Level5 -- add_right_cancel
namespace MyNat
open MyNat
Advanced Addition World
Level 6: add_left_cancel
The theorem add_left_cancel
is the theorem that you can cancel on the left
when you're doing addition -- if t + a = t + b
then a = b
.
There is a three-line proof which ends in exact add_right_cancel a t b
(or even
exact add_right_cancel _ _ _
); this
strategy involves changing the goal to the statement of add_right_cancel
.
Theorem : add_left_cancel
On the set of natural numbers, addition has the left cancellation property.
In other words, if there are natural numbers a, b
and t
such that
if t + a = t + b
then we have a = b
.
theoremadd_left_cancel (add_left_cancel: ∀ (t a b : MyNat), t + a = t + b → a = btt: MyNataa: MyNatb :b: MyNatMyNat) :MyNat: Typet +t: MyNata =a: MyNatt +t: MyNatb →b: MyNata =a: MyNatb :=b: MyNatt, a, b: MyNatt + a = t + b → a = bt, a, b: MyNatt + a = t + b → a = bt, a, b: MyNata + t = t + b → a = bt, a, b: MyNata + t = t + b → a = bt, a, b: MyNata + t = t + b → a = bt, a, b: MyNata + t = b + t → a = bt, a, b: MyNata + t = b + t → a = bGoals accomplished! 🐙
Next up Level 7
import MyNat.Definition
import AdditionWorld.Level4 -- add_comm
import AdvancedAdditionWorld.Level5 -- add_right_cancel
namespace MyNat
open MyNat
Advanced Addition World
Level 7: add_right_cancel_iff
It's sometimes convenient to have the "if and only if" version
of theorems like add_right_cancel
. Remember that you can use constructor
to split an ↔
goal into the →
goal and the ←
goal.
Pro tip:
Notice exact add_right_cancel _ _ _
means "let Lean figure out the missing inputs"
so we don't have to spell it out like we did in Level 6.
Theorem
For all naturals a
, b
and t
, a + t = b + t ↔ a = b.
theoremadd_right_cancel_iff (add_right_cancel_iff: ∀ (t a b : MyNat), a + t = b + t ↔ a = btt: MyNataa: MyNatb :b: MyNatMyNat) :MyNat: Typea +a: MyNatt =t: MyNatb +b: MyNatt ↔t: MyNata =a: MyNatb :=b: MyNatt, a, b: MyNata + t = b + t ↔ a = bt, a, b: MyNat
mpa + t = b + t → a = bt, a, b: MyNata = b → a + t = b + tt, a, b: MyNat
mpra = b → a + t = b + tt, a, b: MyNat
h: a = b
mpra + t = b + tt, a, b: MyNat
h: a = b
mpra + t = b + tt, a, b: MyNat
h: a = b
mprb + t = b + tGoals accomplished! 🐙
Next up Level 8
import MyNat.Definition
import MyNat.Addition -- add_zero
import AdvancedAdditionWorld.Level6 -- add_left_cancel
namespace MyNat
open MyNat
Advanced Addition World
Level 8: eq_zero_of_add_right_eq_self
The lemma you're about to prove will be useful when we want to prove that ≤
is antisymmetric.
There are some wrong paths that you can take with this one.
Lemma
If a
and b
are natural numbers such that
a + b = a,
then b = 0
.
lemmaeq_zero_of_add_right_eq_self {eq_zero_of_add_right_eq_self: ∀ {a b : MyNat}, a + b = a → b = 0aa: MyNatb :b: MyNatMyNat} :MyNat: Typea +a: MyNatb =b: MyNata →a: MyNatb =b: MyNat0 :=0: MyNata, b: MyNata + b = a → b = 0a, b: MyNat
h: a + b = ab = 0a, b: MyNat
h: a + b = a
aa + b = a + 0a, b: MyNat
h: a + b = a
aa + b = a + 0a, b: MyNat
h: a + b = a
aa = a + 0a, b: MyNat
h: a + b = a
aa = a + 0a, b: MyNat
h: a + b = a
aa = a + 0a, b: MyNat
h: a + b = a
aa = aGoals accomplished! 🐙
Remember from FunctionWorld Level 4../FunctonWorld/Level4.lean.md) that the
apply
tactic is can construct the path backwards? Well when we use it with
add_left_cancel a
it results in the opposite of cancellation, it
results in adding a to both sides changing the goal from ⊢ b = 0
to ⊢ a + b = a + 0
. This then allows us to use our hypothesis h : a + b = a
in rw
to complete the proof.
Next up Level 9
import MyNat.Definition
import MyNat.Addition
import Mathlib.Tactic.Relation.Symm
namespace MyNat
open MyNat
axiom zero_ne_succ: ∀ (a : MyNat), 0 ≠ succ a
zero_ne_succ (a: MyNat
a : MyNat: Type
MyNat) : 0: MyNat
0 ≠ succ: MyNat → MyNat
succ a: MyNat
a
Advanced Addition World
Level 9: succ_ne_zero
In this level we will use a new tactic, the symm tactic.
symm
turns goals of the form ⊢ A = B
to ⊢ B = A
.
This tactic is extensible, meaning you can add new @[symm]
attributes to things to teach symm
new tricks, like we
did with the simp
tactic. To teach it how to deal with
≠
we write this:
@[symm] def neqSymm: ∀ {α : Type} (a b : α), a ≠ b → b ≠ a
neqSymm {α: Type
α : Type: Type 1
Type} (a: α
a b: α
b: α: Type
α) : a: α
a ≠ b: α
b → b: α
b ≠ a: α
a := Ne.symm: ∀ {α : Type} {a b : α}, a ≠ b → b ≠ a
Ne.symm
Levels 9 to 13 introduce the last axiom of Peano, namely
that 0 ≠ succ a
. The proof of this is called zero_ne_succ a
.
zero_ne_succ (a : MyNat) : 0 ≠ succ a
We can simply use the symm
tactic to flip this goal into
succ a ≠ 0
which then matches our zero_ne_succ
axiom.
Theorem : succ_ne_zero
Zero is not the successor of any natural number.
theoremsucc_ne_zero (succ_ne_zero: ∀ (a : MyNat), succ a ≠ 0a :a: MyNatMyNat) :MyNat: Typesuccsucc: MyNat → MyNata ≠a: MyNat0 :=0: MyNata: MyNatsucc a ≠ 0a: MyNat0 ≠ succ aGoals accomplished! 🐙
Next up Level 10
import MyNat.Definition
import MyNat.Addition
import AdvancedAdditionWorld.Level9 -- succ_ne_zero
namespace MyNat
open MyNat
Advanced Addition World
Level 10: add_left_eq_zero
Important: the definition of ≠
In Lean, a ≠ b
is defined to mean (a = b) → false
.
This means that if you see a ≠ b
you can literally treat
it as saying (a = b) → false
. Computer scientists would
say that these two terms are definitionally equal.
The following lemma, a+b=0 ⟹ b=0
, will be useful in Inequality World.
Let me go through the proof, because it introduces several new
concepts:
cases b
, whereb : MyNat
exfalso
apply succ_ne_zero
We're going to prove a+b=0 ⟹ b=0
. Here is the
strategy. Each natural number is either 0
or succ d
for
some other natural number d
. So we can start the proof
with
cases b with d
and then we have two goals, the case b = 0
(which you can solve easily)
and the case b = succ d
, which looks like this:
a d : MyNat,
h : a + succ d = 0
⊢ succ d = 0
Our goal is impossible to prove. However our hypothesis h
is also impossible, meaning that we still have a chance!
First let's see why h
is impossible. We can
rw [add_succ] at h
to turn h
into h : succ (a + d) = 0
. Because
succ_ne_zero (a + d)
is a proof that succ (a + d) ≠ 0
,
it is also a proof of the implication succ (a + d) = 0 → false
.
Hence succ_ne_zero (a + d) h
is a proof of false
!
Unfortunately our goal is not false
, it's a generic
false statement.
Recall however that the exfalso
command turns any goal into false
(it's logically OK because false
implies every proposition, true or false).
Lemma
If a
and b
are natural numbers such that a + b = 0
then b = 0
.
lemmaadd_left_eq_zero {{add_left_eq_zero: ∀ ⦃a b : MyNat⦄, a + b = 0 → b = 0aa: MyNatb :b: MyNatMyNat}} (MyNat: Typeh :h: a + b = 0a +a: MyNatb =b: MyNat0) :0: MyNatb =b: MyNat0 :=0: MyNata, b: MyNat
h: a + b = 0b = 0a, b: MyNat
h: a + b = 0b = 0a: MyNat
h: a + zero = 0
zerozero = 0Goals accomplished! 🐙a, d: MyNat
h: a + succ d = 0
succsucc d = 0a, d: MyNat
h: a + succ d = 0
succsucc d = 0a, d: MyNat
h: succ (a + d) = 0
succsucc d = 0a, d: MyNat
h: succ (a + d) = 0
succsucc d = 0a, d: MyNat
h: succ (a + d) = 0
succsucc d = 0a, d: MyNat
h: succ (a + d) = 0
succ.hFalsea, d: MyNat
h: succ (a + d) = 0
succ.hsucc (a + d) = 0Goals accomplished! 🐙
Next up Level 11
import MyNat.Definition
import MyNat.Addition
import AdditionWorld.Level4 -- add_comm
import AdvancedAdditionWorld.Level10 -- add_left_eq_zero
namespace MyNat
open MyNat
Advanced Addition World
Level 11: add_right_eq_zero
We just proved add_left_eq_zero (a b : MyNat) : a + b = 0 → b = 0
.
Hopefully add_right_eq_zero
shouldn't be too hard now.
Lemma
If a
and b
are natural numbers such that if a + b = 0
then a = 0
.
lemmaadd_right_eq_zero {add_right_eq_zero: ∀ {a b : MyNat}, a + b = 0 → a = 0aa: MyNatb :b: MyNatMyNat} :MyNat: Typea +a: MyNatb =b: MyNat0 →0: MyNata =a: MyNat0 :=0: MyNata, b: MyNata + b = 0 → a = 0a, b: MyNat
h: a + b = 0a = 0a, b: MyNat
h: a + b = 0a = 0a, b: MyNat
h: b + a = 0a = 0a, b: MyNat
h: b + a = 0a = 0a, b: MyNat
h: b + a = 0a = 0Goals accomplished! 🐙
Next up Level 12
import MyNat.Definition
import MyNat.Addition
import AdditionWorld.Level5 -- succ_eq_add_one
namespace MyNat
open MyNat
Advanced Addition World
Level 12: add_one_eq_succ
We have
succ_eq_add_one (n : MyNat) : succ n = n + 1
but sometimes the other way is also convenient.
Theorem
For any natural number d
, we have d+1 = succ d.
theoremadd_one_eq_succ (add_one_eq_succ: ∀ (d : MyNat), d + 1 = succ dd :d: MyNatMyNat) :MyNat: Typed +d: MyNat1 =1: MyNatsuccsucc: MyNat → MyNatd :=d: MyNatd: MyNatd + 1 = succ dd: MyNatd + 1 = succ dd: MyNatd + 1 = d + 1Goals accomplished! 🐙
Next up Level 13
import MyNat.Definition
import MyNat.Addition
import AdvancedAdditionWorld.Level1 -- succ_inj
import AdvancedAdditionWorld.Level10 -- zero_ne_succ
namespace MyNat
open MyNat
Advanced Addition World
Level 13: ne_succ_self
The last level in Advanced Addition World is the statement
that n ≠ succ n
.
Lemma
For any natural number n
, we have n ≠ succ n
.
lemmane_succ_self (ne_succ_self: ∀ (n : MyNat), n ≠ succ nn :n: MyNatMyNat) :MyNat: Typen ≠n: MyNatsuccsucc: MyNat → MyNatn :=n: MyNatn: MyNatn ≠ succ nn: MyNatn ≠ succ n
zerozero ≠ succ zeroGoals accomplished! 🐙n: MyNat
ih: n ≠ succ n
succsucc n ≠ succ (succ n)n: MyNat
ih: n ≠ succ n
hs: succ n = succ (succ n)
succFalsen: MyNat
ih: n ≠ succ n
hs: succ n = succ (succ n)
succn = succ nn: MyNat
ih: n ≠ succ n
hs: succ n = succ (succ n)
succ.asucc n = succ (succ n)Goals accomplished! 🐙
Well that's a wrap on Advanced Addition World !
You can now move on to Advanced Multiplication World (after first doing Multiplication World, if you didn't do it already).
import AdvancedMultiplicationWorld.Level1
import AdvancedMultiplicationWorld.Level2
import AdvancedMultiplicationWorld.Level3
-- import AdvancedMultiplicationWorld.Level4 -- bugbug
Advanced Multiplication World
Welcome to Advanced Multiplication World! Before attempting this world you should have completed seven other worlds, including Multiplication World and Advanced Addition World. There are four levels in this world.
Let's dive in Level 1
import MyNat.Definition
import MyNat.Addition -- add_succ
import MyNat.Multiplication -- mul_succ
import AdvancedAdditionWorld.Level9 -- succ_ne_zero
namespace MyNat
open MyNat
Advanced Multiplication World
Level 1: mul_pos
Recall that if b : MyNat
is a hypothesis and you do cases b with
,
your one goal will split into two goals,
namely the cases b = 0
and b = succ n
. So cases
here is like
a weaker version of induction (you don't get the inductive hypothesis).
Tricks
-
if your goal is
⊢ X ≠ Y
thenintro h
will give youh : X = Y
and a goal of⊢ false
. This is becauseX ≠ Y
means(X = Y) → false
. Conversely if your goal isfalse
and you haveh : X ≠ Y
as a hypothesis thenapply h
will work backwards and turn the goal intoX = Y
. -
if
hab : succ (3 * x + 2 * y + 1) = 0
is a hypothesis and your goal is⊢ false
, thenexact succ_ne_zero _ hab
will solve the goal, because Lean will figure out that_
is supposed to be3 * x + 2 * y + 1
.
Theorem
The product of two non-zero natural numbers is non-zero.
theoremmul_pos (mul_pos: ∀ (a b : MyNat), a ≠ 0 → b ≠ 0 → a * b ≠ 0aa: MyNatb :b: MyNatMyNat) :MyNat: Typea ≠a: MyNat0 →0: MyNatb ≠b: MyNat0 →0: MyNata *a: MyNatb ≠b: MyNat0 :=0: MyNata, b: MyNata ≠ 0 → b ≠ 0 → a * b ≠ 0a, b: MyNat
ha: a ≠ 0
hb: b ≠ 0a * b ≠ 0a, b: MyNat
ha: a ≠ 0
hb: b ≠ 0
hab: a * b = 0Falsea, b: MyNat
ha: a ≠ 0
hb: b ≠ 0
hab: a * b = 0Falsea: MyNat
ha: a ≠ 0
hb: zero ≠ 0
hab: a * zero = 0
zeroFalsea: MyNat
ha: a ≠ 0
hb: zero ≠ 0
hab: a * zero = 0
zerozero = 0Goals accomplished! 🐙a: MyNat
ha: a ≠ 0
b': MyNat
hb: succ b' ≠ 0
hab: a * succ b' = 0
succFalsea: MyNat
ha: a ≠ 0
b': MyNat
hb: succ b' ≠ 0
hab: a * succ b' = 0
succFalsea: MyNat
ha: a ≠ 0
b': MyNat
hb: succ b' ≠ 0
hab: a * b' + a = 0
succFalsea: MyNat
ha: a ≠ 0
b': MyNat
hb: succ b' ≠ 0
hab: a * b' + a = 0
succFalsea: MyNat
ha: a ≠ 0
b': MyNat
hb: succ b' ≠ 0
hab: a * b' + a = 0
succFalsea: MyNat
ha: a ≠ 0
b': MyNat
hb: succ b' ≠ 0
hab: a * b' + a = 0
succa = 0a: MyNat
ha: a ≠ 0
b': MyNat
hb: succ b' ≠ 0
hab: a * b' + a = 0
succa = 0b': MyNat
hb: succ b' ≠ 0
ha: zero ≠ 0
hab: zero * b' + zero = 0
succ.zerozero = 0Goals accomplished! 🐙b': MyNat
hb: succ b' ≠ 0
a': MyNat
ha: succ a' ≠ 0
hab: succ a' * b' + succ a' = 0
succ.succsucc a' = 0b': MyNat
hb: succ b' ≠ 0
a': MyNat
ha: succ a' ≠ 0
hab: succ a' * b' + succ a' = 0
succ.succsucc a' = 0b': MyNat
hb: succ b' ≠ 0
a': MyNat
ha: succ a' ≠ 0
hab: succ (succ a' * b' + a') = 0
succ.succsucc a' = 0b': MyNat
hb: succ b' ≠ 0
a': MyNat
ha: succ a' ≠ 0
hab: succ (succ a' * b' + a') = 0
succ.succsucc a' = 0b': MyNat
hb: succ b' ≠ 0
a': MyNat
ha: succ a' ≠ 0
hab: succ (succ a' * b' + a') = 0
succ.succsucc a' = 0b': MyNat
hb: succ b' ≠ 0
a': MyNat
ha: succ a' ≠ 0
hab: succ (succ a' * b' + a') = 0
succ.succ.hFalseGoals accomplished! 🐙
Next up Level 2
import MyNat.Definition
import MyNat.Addition -- add_succ
import MyNat.Multiplication -- mul_succ
import AdvancedAdditionWorld.Level9 -- succ_ne_zero
import Mathlib.Tactic.LeftRight
namespace MyNat
open MyNat
Advanced Multiplication World
Level 2: eq_zero_or_eq_zero_of_mul_eq_zero
A variant on the previous level.
Theorem
If ab = 0
, then at least one of a
or b
is equal to zero.
theoremeq_zero_or_eq_zero_of_mul_eq_zero (eq_zero_or_eq_zero_of_mul_eq_zero: ∀ (a b : MyNat), a * b = 0 → a = 0 ∨ b = 0aa: MyNatb :b: MyNatMyNat) (MyNat: Typeh :h: a * b = 0a *a: MyNatb =b: MyNat0) :0: MyNata =a: MyNat0 ∨0: MyNatb =b: MyNat0 :=0: MyNata, b: MyNat
h: a * b = 0a = 0 ∨ b = 0a, b: MyNat
h: a * b = 0a = 0 ∨ b = 0b: MyNat
h: zero * b = 0
zerozero = 0 ∨ b = 0b: MyNat
h: zero * b = 0
zero.hzero = 0Goals accomplished! 🐙b, a': MyNat
h: succ a' * b = 0
succsucc a' = 0 ∨ b = 0b, a': MyNat
h: succ a' * b = 0
succsucc a' = 0 ∨ b = 0a': MyNat
h: succ a' * zero = 0
succ.zerosucc a' = 0 ∨ zero = 0a': MyNat
h: succ a' * zero = 0
succ.zero.hzero = 0Goals accomplished! 🐙a', b': MyNat
h: succ a' * succ b' = 0
succ.succsucc a' = 0 ∨ succ b' = 0a', b': MyNat
h: succ a' * succ b' = 0
succ.succ.hFalsea', b': MyNat
h: succ a' * succ b' = 0
succ.succ.hFalsea', b': MyNat
h: succ a' * b' + succ a' = 0
succ.succ.hFalsea', b': MyNat
h: succ a' * b' + succ a' = 0
succ.succ.hFalsea', b': MyNat
h: succ a' * b' + succ a' = 0
succ.succ.hFalsea', b': MyNat
h: succ a' * b' + succ a' = 0
succ.succ.hFalsea', b': MyNat
h: succ (succ a' * b' + a') = 0
succ.succ.hFalsea', b': MyNat
h: succ (succ a' * b' + a') = 0
succ.succ.hFalsea', b': MyNat
h: succ (succ a' * b' + a') = 0
succ.succ.hFalseGoals accomplished! 🐙
Next up Level 3
import MyNat.Definition
import MultiplicationWorld.Level1 -- zero_mul
import AdvancedMultiplicationWorld.Level2 -- eq_zero_or_eq_zero_of_mul_eq_zero
import Mathlib.Tactic.LeftRight
namespace MyNat
open MyNat
Advanced Multiplication World
Level 3: mul_eq_zero_iff
Now you have eq_zero_or_eq_zero_of_mul_eq_zero
this is pretty straightforward.
Theorem
ab = 0
, if and only if at least one of a
or b
is equal to zero.
theoremmul_eq_zero_iff (mul_eq_zero_iff: ∀ (a b : MyNat), a * b = 0 ↔ a = 0 ∨ b = 0aa: MyNatb :b: MyNatMyNat):MyNat: Typea *a: MyNatb =b: MyNat0 ↔0: MyNata =a: MyNat0 ∨0: MyNatb =b: MyNat0 :=0: MyNata, b: MyNata * b = 0 ↔ a = 0 ∨ b = 0a, b: MyNat
mpa * b = 0 → a = 0 ∨ b = 0a, b: MyNata = 0 ∨ b = 0 → a * b = 0a, b: MyNat
mpa * b = 0 → a = 0 ∨ b = 0a, b: MyNat
h: a * b = 0
mpa = 0 ∨ b = 0Goals accomplished! 🐙a, b: MyNat
mpra = 0 ∨ b = 0 → a * b = 0a, b: MyNat
mpra = 0 ∨ b = 0 → a * b = 0a, b: MyNat
hab: a = 0 ∨ b = 0
mpra * b = 0a, b: MyNat
hab: a = 0 ∨ b = 0
mpra * b = 0a, b: MyNat
ha: a = 0
mpr.inla * b = 0a, b: MyNat
ha: a = 0
mpr.inla * b = 0a, b: MyNat
ha: a = 0
mpr.inl0 * b = 0a, b: MyNat
ha: a = 0
mpr.inl0 * b = 0a, b: MyNat
ha: a = 0
mpr.inl0 * b = 0a, b: MyNat
ha: a = 0
mpr.inl0 = 0Goals accomplished! 🐙a, b: MyNat
hb: b = 0
mpr.inra * b = 0a, b: MyNat
hb: b = 0
mpr.inra * b = 0a, b: MyNat
hb: b = 0
mpr.inra * 0 = 0a, b: MyNat
hb: b = 0
mpr.inra * 0 = 0a, b: MyNat
hb: b = 0
mpr.inra * 0 = 0a, b: MyNat
hb: b = 0
mpr.inr0 = 0Goals accomplished! 🐙Goals accomplished! 🐙
Next up Level 4
import MyNat.Definition
import MultiplicationWorld.Level6 -- succ_mul
import MyNat.Multiplication -- mul_succ, mul_zero
import AdvancedMultiplicationWorld.Level2 -- eq_zero_or_eq_zero_of_mul_eq_zero
namespace MyNat
open MyNat
Advanced Multiplication World
Level 4: mul_left_cancel
This is the last of the bonus multiplication levels. mul_left_cancel
will be useful in inequality
world.
People find this level hard. I have probably had more questions about this level than all the other levels put together, in fact. Many levels in this game can just be solved by "running at it" -- do induction on one of the variables, keep your head, and you're done. In fact, if you like a challenge, it might be instructive if you stop reading after the end of this paragraph and try solving this level now by induction, seeing the trouble you run into, and reading the rest of these comments afterwards. This level has a sting in the tail. If you are a competent mathematician, try and figure out what is going on. Write down a maths proof of the theorem in this level. Exactly what statement do you want to prove by induction? It is subtle.
Ok so here are some spoilers. The problem with naively running at it, is that if you try induction
on, say, c
, then you are imagining a
and b
as fixed, and your inductive hypothesis P(c)
is
ab=ac ⟹ b=c
. So for your inductive step you will be able to assume ab=ad ⟹ b=d
and your goal
will be to show ab=a(d+1) ⟹ b=d+1
. When you also assume ab=a(d+1)
you will realize that your
inductive hypothesis is useless, because ab=ad
is not true! The statement P(c)
(with a
and
b
regarded as constants) is not provable by induction.
What you can prove by induction is the following stronger statement. Imagine a ≠ 0
as fixed,
and then prove "for all b
, if ab=ac
then b=c
" by induction on c
. This gives us the extra
flexibility we require. Note that we are quantifying over all b
in the inductive hypothesis -- it
is essential that b
is not fixed.
You can do this in two ways in Lean -- before you start the induction you can write revert b
. The
revert
tactic is the opposite of the intro
tactic; it replaces the b
in the hypotheses with
"for all b
" in the goal.
Alternatively, you can write induction c generalizing b with
as the first line of the proof.
If you do not modify your technique in this way, then this level seems to be impossible (judging by the comments I've had about it!)
Theorem
If a ≠ 0
, b
and c
are natural numbers such that ab = ac,
then b = c
.
set_option trace.Meta.Tactic.simp true theoremmul_left_cancel (mul_left_cancel: ∀ (a b c : MyNat), a ≠ 0 → a * b = a * c → b = caa: MyNatbb: MyNatc :c: MyNatMyNat) (MyNat: Typeha :ha: a ≠ 0a ≠a: MyNat0) :0: MyNata *a: MyNatb =b: MyNata *a: MyNatc →c: MyNatb =b: MyNatc :=c: MyNata, b, c: MyNat
ha: a ≠ 0a * b = a * c → b = ca, b, c: MyNat
ha: a ≠ 0a * b = a * c → b = ca: MyNat
ha: a ≠ 0
b: MyNat
zeroa * b = a * zero → b = zeroa: MyNat
ha: a ≠ 0
b: MyNat
zeroa * b = a * zero → b = zeroa: MyNat
ha: a ≠ 0
b: MyNat
zeroa * b = a * 0 → b = 0a: MyNat
ha: a ≠ 0
b: MyNat
zeroa * b = a * 0 → b = 0a: MyNat
ha: a ≠ 0
b: MyNat
zeroa * b = a * 0 → b = 0a: MyNat
ha: a ≠ 0
b: MyNat
zeroa * b = 0 → b = 0a: MyNat
ha: a ≠ 0
b: MyNat
zeroa * b = 0 → b = 0a: MyNat
ha: a ≠ 0
b: MyNat
h: a * b = 0
zerob = 0a: MyNat
ha: a ≠ 0
b: MyNat
h: a * b = 0
x✝: a = 0 ∨ b = 0
zerob = 0a: MyNat
ha: a ≠ 0
b: MyNat
h: a * b = 0
h1: a = 0
zero.inlb = 0a: MyNat
ha: a ≠ 0
b: MyNat
h: a * b = 0
h1: a = 0
zero.inl.hFalsea: MyNat
ha: a ≠ 0
b: MyNat
h: a * b = 0
h1: a = 0
zero.inl.ha = 0Goals accomplished! 🐙a: MyNat
ha: a ≠ 0
b: MyNat
h: a * b = 0
h2: b = 0
zero.inrb = 0Goals accomplished! 🐙a: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
b: MyNat
succa * b = a * succ d → b = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
b: MyNat
hb: a * b = a * succ d
succb = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
b: MyNat
hb: a * b = a * succ d
succb = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: a * zero = a * succ d
succ.zerozero = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: a * zero = a * succ d
succ.zerozero = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: a * 0 = a * succ d
succ.zerozero = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: a * 0 = a * succ d
succ.zerozero = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: a * 0 = a * succ d
succ.zerozero = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: a * 0 = a * succ d
succ.zerozero = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: 0 = a * succ d
succ.zerozero = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: 0 = a * succ d
succ.zerozero = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: 0 = a * succ d
succ.zerozero = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: 0 = a * succ d
succ.zerozero = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: 0 = a * succ d
succ.zero0 = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: 0 = a * succ d
succ.zero0 = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: 0 = a * succ d
succ.zero.hFalsea: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb: 0 = a * succ d
succ.zero.ha = 0a: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb✝: 0 = a * succ d
hb: a * succ d = 0
succ.zero.ha = 0a: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb✝: 0 = a * succ d
hb: a * succ d = 0
x✝: a = 0 ∨ succ d = 0
succ.zero.ha = 0a: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb✝: 0 = a * succ d
hb: a * succ d = 0
h: a = 0
succ.zero.h.inla = 0Goals accomplished! 🐙a: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb✝: 0 = a * succ d
hb: a * succ d = 0
h: succ d = 0
succ.zero.h.inra = 0a: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
hb✝: 0 = a * succ d
hb: a * succ d = 0
h: succ d = 0
succ.zero.h.inr.hFalseGoals accomplished! 🐙a: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
c: MyNat
hb: a * succ c = a * succ d
succ.succsucc c = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
c: MyNat
hb: a * succ c = a * succ d
h: Prop
succ.succsucc c = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
c: MyNat
hb: a * succ c = a * succ d
h: Prop
succ.succsucc c = succ da: MyNat
ha: a ≠ 0
d: MyNat
hd: ∀ (b : MyNat), a * b = a * d → b = d
c: MyNat
hb: a * succ c = a * succ d
h: Prop
succ.succsucc c = succ d
You should now be ready for Inequality World.
import InequalityWorld.Level1
import InequalityWorld.Level2
import InequalityWorld.Level3
import InequalityWorld.Level4
import InequalityWorld.Level5
import InequalityWorld.Level6
import InequalityWorld.Level7
import InequalityWorld.Level8
import InequalityWorld.Level9
import InequalityWorld.Level10
import InequalityWorld.Level11
import InequalityWorld.Level12
import InequalityWorld.Level13
import InequalityWorld.Level14
import InequalityWorld.Level15
import InequalityWorld.Level16
import InequalityWorld.Level17
-- import InequalityWorld.Level18 -- BUGBUG
Inequality world.
A new import MyNat.Inequality
gives us a new definition. If a
and b
are naturals,
a ≤ b
is defined to mean
∃ (c : MyNat), b = a + c
The backwards E means "there exists". So in words, a ≤ b
if and only if there exists a natural c
such that b=a+c
.
If you really want to change an a ≤ b
to ∃ c, b = a + c
then
you can do so with rw [le_iff_exists_add]
:
axiom le_iff_exists_add (a b : MyNat) :
a ≤ b ↔ ∃ (c : MyNat), b = a + c
But because a ≤ b
is defined as ∃ (c : MyNat), b = a + c
, you
do not need to rw [le_iff_exists_add]
, you can just pretend when you see a ≤ b
that it says ∃ (c : MyNat), b = a + c
. You will see a concrete
example of this in level 1.
A new construction like ∃
means that we need to learn how to manipulate it.
There are two situations. Firstly we need to know how to solve a goal
of the form ⊢ ∃ c, ...
, and secondly we need to know how to use a hypothesis
of the form ∃ c, ...
.
Let's dive in Level 1
import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import AdditionWorld.Level4 -- add_comm
namespace MyNat
open MyNat
Inequality world.
Level 1: the use
tactic
The goal below is to prove x ≤ 1+x
for any natural number x
.
First let's turn the goal explicitly into an existence problem with
rw [le_iff_exists_add]
and now the goal has become ∃ c : MyNat, 1 + x = x + c
. Clearly
this statement is true, and the proof is that c=1
will work (we also
need the fact that addition is commutative, but we proved that a long
time ago). How do we make progress with this goal?
The use
tactic can be used on goals of the form ∃ c, ...
. The idea
is that we choose which natural number we want to use, and then we use it.
So try
use 1
and now the goal becomes ⊢ 1 + x = x + 1
. You can solve this by
exact add_comm 1 x
, or if you are lazy you can just use the ring
tactic,
which is a powerful AI which will solve any equality in algebra which can
be proved using the standard rules of addition and multiplication. Now
look at your proof. We're going to remove a line.
Important
An important time-saver here is to note that because a ≤ b
is defined
as ∃ c : MyNat, b = a + c
, you do not need to write rw [le_iff_exists_add]
.
The use
tactic will work directly on a goal of the form a ≤ b
. Just
use the difference b - a
(note that we have not defined subtraction so
this does not formally make sense, but you can do the calculation in your head).
If you have written rw [le_iff_exists_add]
below, then just put two minus signs --
before it and comment it out. See that the proof still compiles.
Lemma : one_add_le_self
If x
is a natural number, then x ≤ 1+x
.
lemmaone_add_le_self (one_add_le_self: ∀ (x : MyNat), x ≤ 1 + xx :x: MyNatMyNat) :MyNat: Typex ≤x: MyNat1 +1: MyNatx :=x: MyNatx: MyNatx ≤ 1 + xx: MyNatx ≤ 1 + xx: MyNat∃ c, 1 + x = x + cx: MyNat∃ c, 1 + x = x + cx: MyNat1 + x = x + 1x: MyNat1 + x = x + 1x: MyNatx + 1 = x + 1Goals accomplished! 🐙
Next up Level 2
import MyNat.Definition
import MyNat.Addition -- add_zero
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import Mathlib.Tactic.Relation.Rfl -- rfl tactic
import AdditionWorld.Level4 -- add_comm
namespace MyNat
open MyNat
Inequality world.
Here's a nice easy one.
Level 2: le_refl
Lemma :
The ≤
relation is reflexive. In other words, if x
is a natural number,
then x ≤ x
.
lemmale_refl_mynat (le_refl_mynat: ∀ (x : MyNat), x ≤ xx :x: MyNatMyNat) :MyNat: Typex ≤x: MyNatx :=x: MyNatx: MyNatx ≤ xGoals accomplished! 🐙
Upgrading the rfl
tactic
Now with the following incantation you can teach the MathLib rfl tactic about our new lemma.
attribute [refl] MyNat.le_refl_mynat: ∀ (x : MyNat), x ≤ x
MyNat.le_refl_mynat
Now we find that the rfl
tactic will close all goals
of the form a ≤ a
as well as all goals of the form a = a
.
example : (example: 0 ≤ 00 :0: MyNatMyNat) ≤MyNat: Type0 :=0: MyNat0 ≤ 0Goals accomplished! 🐙
Pro tip
-- BUGBUG in lean4 use 0
closes the proof, so no need for all this stuff
below -- so I need to move this info to another place where it makes sense.
Did you skip rw [le_iff_exists_add]
in your proof of le_refl_mynat
above?
Instead of rw [add_zero]
or ring
or exact add_zero x
at the end there,
what happens if you just try rfl
? The definition of x + 0
is x
,
so you don't need to rw add_zero
either! The proof
use 0
works.
The same remarks are true of
add_succ
, mul_zero
, mul_succ
, pow_zero
and pow_succ
. All of those
theorems are true by definition. The same is not true however of zero_add
;
the theorem 0 + x = x
was proved by induction on x
,
and in particular it is not true by definition.
Definitional equality is of great importance
to computer scientists, but mathematicians are much more fluid with their idea
of a definition -- a concept can simultaneously have three equivalent definitions
in a maths talk, as long as they're all logically equivalent. In Lean, a definition
is one thing, and definitional equality is a subtle concept which depends on
exactly which definition you chose. add_comm
is certainly not true by definition,
which means that if we had decided to define a ≤ b
by ∃ c, b = c + a
(rather
than a + c
) all the same theorems would be true, but rfl
would work in
different places. rfl
closes a goal of the form X = Y
if X
and Y
are
definitionally equal.
Next up Level 3
import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import AdditionWorld.Level4 -- add_comm
namespace MyNat
open MyNat
Inequality world.
Level 3: le_succ_of_le
We have seen how the use
tactic makes progress on goals of the form ⊢ ∃ c, ...
.
But what do we do when we have a hypothesis of the form h : ∃ c, ...
?
The hypothesis claims that there exists some natural number c
with some
property. How are we going to get to that natural number c
? It turns out
that the cases
tactic can be used (just like it was used to extract
information from ∧
and ∨
and ↔
hypotheses). Let me talk you through
the proof of a ≤ b ⟹ a ≤ succ b
.
The goal is an implication so we clearly want to start with
intro h
After this, if you want, you can do something like
rw [le_iff_exists_add] at h ⊢
(get the sideways T with \|-
or \goal
then space). This rw
changes the ≤
into
its ∃
form in h
and the goal -- but if you are happy with just
imagining the ∃
whenever you read a ≤
then you don't need to do this line.
Our hypothesis h
is now ∃ (c : MyNat), b = a + c
(or a ≤ b
if you
elected not to do the definitional rewriting) so
cases h with | _ c hc => ..
gives you the natural number c
and the hypothesis hc : b = a + c
.
Now use use
wisely and you're home.
Lemma : le_succ
For all naturals a
, b
, if a ≤ b
then a ≤ succ b
.
theoremle_succ (le_succ: ∀ (a b : MyNat), a ≤ b → a ≤ succ baa: MyNatb :b: MyNatMyNat) :MyNat: Typea ≤a: MyNatb →b: MyNata ≤ (a: MyNatsuccsucc: MyNat → MyNatb) :=b: MyNata, b: MyNata ≤ b → a ≤ succ ba, b: MyNat
h: a ≤ ba ≤ succ ba, b: MyNat
h: a ≤ ba ≤ succ ba, b, c: MyNat
hc: b = a + c
introa ≤ succ ba, b, c: MyNat
hc: b = a + c
introa ≤ succ ba, b, c: MyNat
hc: b = a + c
introa ≤ succ (a + c)a, b, c: MyNat
hc: b = a + c
introa ≤ succ (a + c)Goals accomplished! 🐙
You can use succ c
or c + 1
or 1 + c
. Those numbers are all
equal, right? Try these variations and see what happens.
Now what about if you do use 1 + c
? Can you work out
what is going on? Does it help if I tell you that the definition
of 1
is succ 0
?
Next up Level 4
import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import AdditionWorld.Level1 -- zero_add
namespace MyNat
open MyNat
Inequality world.
Level 4: zero_le
Another easy one.
Lemma : zero_le
For all naturals a
, 0 ≤ a
.
lemmazero_le (zero_le: ∀ (a : MyNat), 0 ≤ aa :a: MyNatMyNat) :MyNat: Type0 ≤0: MyNata :=a: MyNata: MyNat0 ≤ aa: MyNata = 0 + aa: MyNata = 0 + aa: MyNata = aGoals accomplished! 🐙
Next up Level 5
import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import AdditionWorld.Level2 -- add_assoc
import InequalityWorld.Level2 -- le_refl_mynat
import Mathlib.Init.Algebra.Order
namespace MyNat
open MyNat
Inequality world.
Level 5: le_trans
Another straightforward one.
Lemma : le_trans
≤ is transitive. In other words, if a ≤ b
and b ≤ c
then a ≤ c
.
theoremle_trans (le_trans: ∀ (a b c : MyNat), a ≤ b → b ≤ c → a ≤ caa: MyNatbb: MyNatc :c: MyNatMyNat) (MyNat: Typehab :hab: a ≤ ba ≤a: MyNatb) (b: MyNathbc :hbc: b ≤ cb ≤b: MyNatc) :c: MyNata ≤a: MyNatc :=c: MyNata, b, c: MyNat
hab: a ≤ b
hbc: b ≤ ca ≤ ca, b, c: MyNat
hab: a ≤ b
hbc: b ≤ ca ≤ ca, b, c: MyNat
hbc: b ≤ c
d: MyNat
hd: b = a + d
introa ≤ ca, b, c: MyNat
hbc: b ≤ c
d: MyNat
hd: b = a + d
introa ≤ ca, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e
intro.introa ≤ ca, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e
intro.introc = a + (d + e)a, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e
intro.introc = a + (d + e)a, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e
intro.introc = a + d + ea, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e
intro.introc = a + d + ea, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e
intro.introc = a + d + ea, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e
intro.introc = b + ea, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e
intro.introc = b + eGoals accomplished! 🐙
This proved that the natural numbers are a preorder.
instance: Preorder MyNat
instance : Preorder: Type → Type
Preorder MyNat: Type
MyNat :=
⟨le_refl_mynat: ∀ (x : MyNat), x ≤ x
le_refl_mynat, le_trans: ∀ (a b c : MyNat), a ≤ b → b ≤ c → a ≤ c
le_trans, lt: ∀ (a b : MyNat), a < b ↔ a ≤ b ∧ ¬b ≤ a
lt⟩
Next up Level 6
import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import AdditionWorld.Level2 -- add_assoc
import AdvancedAdditionWorld.Level8 -- eq_zero_of_add_right_eq_self
import AdvancedAdditionWorld.Level11 -- add_right_eq_zero
namespace MyNat
open MyNat
Inequality world.
Level 6: le_antisymm
In Advanced Addition World you proved
eq_zero_of_add_right_eq_self (a b : MyNat) : a + b = a → b = 0
.
This might be useful in this level.
Another tip: if you want to create a new hypothesis, you can use the have
tactic.
For example, if you have a hypothesis hd : a + (c + d) = a
and you want
a hypothesis h : c + d = 0
then you can write
have h := eq_zero_of_add_right_eq_self hd
Lemma : le_antisymm
≤
is antisymmetric. In other words, if a ≤ b
and b ≤ a
then a = b
.
theoremle_antisymm (le_antisymm: ∀ (a b : MyNat), a ≤ b → b ≤ a → a = baa: MyNatb :b: MyNatMyNat) (MyNat: Typehab :hab: a ≤ ba ≤a: MyNatb) (b: MyNathba :hba: b ≤ ab ≤b: MyNata) :a: MyNata =a: MyNatb :=b: MyNata, b: MyNat
hab: a ≤ b
hba: b ≤ aa = ba, b: MyNat
hab: a ≤ b
hba: b ≤ aa = ba, b: MyNat
hba: b ≤ a
c: MyNat
hc: b = a + c
introa = ba, b: MyNat
hba: b ≤ a
c: MyNat
hc: b = a + c
introa = ba, b, c: MyNat
hc: b = a + c
d: MyNat
hd: a = b + d
intro.introa = ba, b, c: MyNat
hc: b = a + c
d: MyNat
hd: a = b + d
intro.introa = ba, b, c: MyNat
hc: b = a + c
d: MyNat
hd: a = a + c + d
intro.introa = ba, b, c: MyNat
hc: b = a + c
d: MyNat
hd: a = a + (c + d)
intro.introa = ba, b, c: MyNat
hc: b = a + c
d: MyNat
hd: a = a + (c + d)
intro.introa = ba, b, c: MyNat
hc: b = a + c
d: MyNat
hd: a = a + (c + d)
intro.introa = ba, b, c: MyNat
hc: b = a + c
d: MyNat
hd✝: a = a + (c + d)
hd: a + (c + d) = a
intro.introa = ba, b, c: MyNat
hc: b = a + c
d: MyNat
hd✝: a = a + (c + d)
hd: a + (c + d) = a
h: c + d = 0
intro.introa = ba, b, c: MyNat
hc: b = a + c
d: MyNat
hd✝: a = a + (c + d)
hd: a + (c + d) = a
h: c + d = 0
h2: c = 0
intro.introa = ba, b, c: MyNat
hc: b = a + c
d: MyNat
hd✝: a = a + (c + d)
hd: a + (c + d) = a
h: c + d = 0
h2: c = 0
intro.introa = ba, b, c: MyNat
hc: b = a + 0
d: MyNat
hd✝: a = a + (c + d)
hd: a + (c + d) = a
h: c + d = 0
h2: c = 0
intro.introa = ba, b, c: MyNat
hc: b = a + 0
d: MyNat
hd✝: a = a + (c + d)
hd: a + (c + d) = a
h: c + d = 0
h2: c = 0
intro.introa = ba, b, c: MyNat
hc: b = a + 0
d: MyNat
hd✝: a = a + (c + d)
hd: a + (c + d) = a
h: c + d = 0
h2: c = 0
intro.introa = ba, b, c: MyNat
hc: b = a + 0
d: MyNat
hd✝: a = a + (c + d)
hd: a + (c + d) = a
h: c + d = 0
h2: c = 0
intro.introa = ba, b, c: MyNat
hc: b = a + 0
d: MyNat
hd✝: a = a + (c + d)
hd: a + (c + d) = a
h: c + d = 0
h2: c = 0
intro.introa = a + 0a, b, c: MyNat
hc: b = a + 0
d: MyNat
hd✝: a = a + (c + d)
hd: a + (c + d) = a
h: c + d = 0
h2: c = 0
intro.introa = a + 0Goals accomplished! 🐙
This proved that the natural numbers are a partial order!
-- BUGBUG : collectibles
-- instance : partial_order MyNat := by structure_helper
Next up Level 7
import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import AdvancedAdditionWorld.Level11 -- add_right_eq_zero
namespace MyNat
open MyNat
Inequality world
Level 7: le_zero
We proved add_right_eq_zero
back in advanced addition world.
Remember that you can do things like have h2 := add_right_eq_zero h1
if h1 : a + c = 0
.
Lemma : le_zero
For all naturals a
, if a ≤ 0
then a = 0
.
lemmale_zero (le_zero: ∀ (a : MyNat), a ≤ 0 → a = 0a :a: MyNatMyNat) (MyNat: Typeh :h: a ≤ 0a ≤a: MyNat0) :0: MyNata =a: MyNat0 :=0: MyNata: MyNat
h: a ≤ 0a = 0a: MyNat
h: a ≤ 0a = 0a, c: MyNat
hc: 0 = a + c
introa = 0a, c: MyNat
hc✝: 0 = a + c
hc: a + c = 0
introa = 0Goals accomplished! 🐙
Next up Level 8
import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import AdvancedAdditionWorld.Level11 -- add_right_eq_zero
namespace MyNat
open MyNat
Inequality world.
Level 8: succ_le_succ
Another straightforward one.
Lemma : succ_le_succ
For all naturals a
and b
, if a ≤ b
, then succ a ≤ succ b
.
lemmasucc_le_succ (succ_le_succ: ∀ (a b : MyNat), a ≤ b → succ a ≤ succ baa: MyNatb :b: MyNatMyNat) (MyNat: Typeh :h: a ≤ ba ≤a: MyNatb) :b: MyNatsuccsucc: MyNat → MyNata ≤a: MyNatsuccsucc: MyNat → MyNatb :=b: MyNata, b: MyNat
h: a ≤ bsucc a ≤ succ ba, b: MyNat
h: a ≤ bsucc a ≤ succ ba, b, c: MyNat
hc: b = a + c
introsucc a ≤ succ ba, b, c: MyNat
hc: b = a + c
introsucc b = succ a + ca, b, c: MyNat
hc: b = a + c
introsucc b = succ a + ca, b, c: MyNat
hc: b = a + c
introsucc (a + c) = succ a + ca, b, c: MyNat
hc: b = a + c
introsucc (a + c) = succ a + ca, b, c: MyNat
hc: b = a + c
introsucc (a + c) = succ a + ca, b, c: MyNat
hc: b = a + c
introsucc (a + c) = succ (a + c)Goals accomplished! 🐙
Next up Level 9
import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import AdvancedAdditionWorld.Level11 -- add_right_eq_zero
import Mathlib.Tactic.LeftRight
import InequalityWorld.Level4 -- zero_le
import InequalityWorld.Level8 -- succ_le_succ
namespace MyNat
open MyNat
Inequality world.
Level 9: le_total
Lemma : le_total
For all naturals a
and b
, either a ≤ b
or b ≤ a
.
theoremle_total (le_total: ∀ (a b : MyNat), a ≤ b ∨ b ≤ aaa: MyNatb :b: MyNatMyNat) :MyNat: Typea ≤a: MyNatb ∨b: MyNatb ≤b: MyNata :=a: MyNata, b: MyNata ≤ b ∨ b ≤ ab: MyNat∀ (a : MyNat), a ≤ b ∨ b ≤ ab: MyNat∀ (a : MyNat), a ≤ b ∨ b ≤ a
zero∀ (a : MyNat), a ≤ zero ∨ zero ≤ aa: MyNat
zeroa ≤ zero ∨ zero ≤ aa: MyNat
zero.hzero ≤ aGoals accomplished! 🐙d: MyNat
hd: ∀ (a : MyNat), a ≤ d ∨ d ≤ a
succ∀ (a : MyNat), a ≤ succ d ∨ succ d ≤ ad: MyNat
hd: ∀ (a : MyNat), a ≤ d ∨ d ≤ a
a: MyNat
succa ≤ succ d ∨ succ d ≤ ad: MyNat
hd: ∀ (a : MyNat), a ≤ d ∨ d ≤ a
a: MyNat
succa ≤ succ d ∨ succ d ≤ ad: MyNat
hd: ∀ (a : MyNat), a ≤ d ∨ d ≤ a
succ.zerozero ≤ succ d ∨ succ d ≤ zerod: MyNat
hd: ∀ (a : MyNat), a ≤ d ∨ d ≤ a
succ.zero.hzero ≤ succ dGoals accomplished! 🐙d: MyNat
hd: ∀ (a : MyNat), a ≤ d ∨ d ≤ a
a: MyNat
succ.succsucc a ≤ succ d ∨ succ d ≤ succ ad: MyNat
hd: ∀ (a : MyNat), a ≤ d ∨ d ≤ a
a: MyNat
h2: a ≤ d ∨ d ≤ a
succ.succsucc a ≤ succ d ∨ succ d ≤ succ ad: MyNat
hd: ∀ (a : MyNat), a ≤ d ∨ d ≤ a
a: MyNat
h2: a ≤ d ∨ d ≤ a
succ.succsucc a ≤ succ d ∨ succ d ≤ succ ad: MyNat
hd: ∀ (a : MyNat), a ≤ d ∨ d ≤ a
a: MyNat
h: a ≤ d
succ.succ.inlsucc a ≤ succ d ∨ succ d ≤ succ ad: MyNat
hd: ∀ (a : MyNat), a ≤ d ∨ d ≤ a
a: MyNat
h: a ≤ d
succ.succ.inl.hsucc a ≤ succ dGoals accomplished! 🐙d: MyNat
hd: ∀ (a : MyNat), a ≤ d ∨ d ≤ a
a: MyNat
h: d ≤ a
succ.succ.inrsucc a ≤ succ d ∨ succ d ≤ succ ad: MyNat
hd: ∀ (a : MyNat), a ≤ d ∨ d ≤ a
a: MyNat
h: d ≤ a
succ.succ.inr.hsucc d ≤ succ aGoals accomplished! 🐙
See the revert tactic
Note in the above proof that exact succ_le_succ a d h
is just shorthand for:
apply succ_le_succ a d exact h
Another collectible: the naturals are a linear order.
-- BUGBUG: collectibles -- instance : linear_order MyNat := by structure_helper
Next up Level 10
import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
namespace MyNat
open MyNat
Inequality world.
Level 10: le_succ_self
Another simple one.
Lemma : le_succ_self
For all naturals a
, a ≤ succ a.
lemmale_succ_self (le_succ_self: ∀ (a : MyNat), a ≤ succ aa :a: MyNatMyNat) :MyNat: Typea ≤a: MyNatsuccsucc: MyNat → MyNata :=a: MyNata: MyNata ≤ succ aGoals accomplished! 🐙
Next up Level 11
import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import AdditionWorld.Level6 -- add_right_comm
namespace MyNat
open MyNat
Inequality world.
Level 11: add_le_add_right
If you're faced with a goal of the form forall t, ...
, then the next
line is "so let t
be arbitrary". The way to do this in Lean is intro t
.
Lemma : add_le_add_right
For all naturals a
and b
, a ≤ b
implies that for all naturals t
,
a+t ≤ b+t
.
theoremadd_le_add_right {add_le_add_right: ∀ {a b : MyNat}, a ≤ b → ∀ (t : MyNat), a + t ≤ b + taa: MyNatb :b: MyNatMyNat} :MyNat: Typea ≤a: MyNatb → ∀b: MyNatt, (t: MyNata +a: MyNatt) ≤ (t: MyNatb +b: MyNatt) :=t: MyNata, b: MyNata ≤ b → ∀ (t : MyNat), a + t ≤ b + ta, b: MyNat
h: a ≤ b∀ (t : MyNat), a + t ≤ b + ta, b: MyNat
h: a ≤ b∀ (t : MyNat), a + t ≤ b + ta, b, c: MyNat
hc: b = a + c
intro∀ (t : MyNat), a + t ≤ b + ta, b, c: MyNat
hc: b = a + c
t: MyNat
introa + t ≤ b + ta, b, c: MyNat
hc: b = a + c
t: MyNat
introb + t = a + t + ca, b, c: MyNat
hc: b = a + c
t: MyNat
introb + t = a + t + ca, b, c: MyNat
hc: b = a + c
t: MyNat
introa + c + t = a + t + ca, b, c: MyNat
hc: b = a + c
t: MyNat
introa + c + t = a + t + ca, b, c: MyNat
hc: b = a + c
t: MyNat
introa + c + t = a + t + ca, b, c: MyNat
hc: b = a + c
t: MyNat
introa + t + c = a + t + cGoals accomplished! 🐙
Next up Level 12
import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import AdditionWorld.Level6 -- add_right_comm
import AdvancedAdditionWorld.Level1 -- succ_inj
namespace MyNat
open MyNat
Inequality world.
Level 12: le_of_succ_le_succ
Lemma : le_of_succ_le_succ
For all naturals a
and b
, succ a ≤ succ b ⟹ a ≤ b.
theoremle_of_succ_le_succ (le_of_succ_le_succ: ∀ (a b : MyNat), succ a ≤ succ b → a ≤ baa: MyNatb :b: MyNatMyNat) :MyNat: Typesuccsucc: MyNat → MyNata ≤a: MyNatsuccsucc: MyNat → MyNatb →b: MyNata ≤a: MyNatb :=b: MyNata, b: MyNatsucc a ≤ succ b → a ≤ ba, b: MyNat
h: succ a ≤ succ ba ≤ ba, b: MyNat
h: succ a ≤ succ ba ≤ ba, b, c: MyNat
hc: succ b = succ a + c
introa ≤ ba, b, c: MyNat
hc: succ b = succ a + c
introb = a + ca, b, c: MyNat
hc: succ b = succ a + c
intro.asucc b = succ (a + c)a, b, c: MyNat
hc: succ b = succ a + c
intro.asucc b = succ (a + c)a, b, c: MyNat
hc: succ b = succ a + c
intro.asucc a + c = succ (a + c)a, b, c: MyNat
hc: succ b = succ a + c
intro.asucc a + c = succ (a + c)Goals accomplished! 🐙
Next up Level 13
import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import AdvancedAdditionWorld.Level1 -- succ_inj
import AdvancedAdditionWorld.Level9 -- zero_ne_succ
namespace MyNat
open MyNat
Inequality world.
Level 13: not_succ_le_self
Turns out that ¬ P
is by definition P → false
, so you can just
start this one with intro h
if you like.
Lemma : not_succ_le_self
For all naturals a
, succ a
is not at most a
.
theoremnot_succ_le_self (not_succ_le_self: ∀ (a : MyNat), ¬succ a ≤ aa :a: MyNatMyNat) : ¬ (MyNat: Typesuccsucc: MyNat → MyNata ≤a: MyNata) :=a: MyNata: MyNat¬succ a ≤ aa: MyNat
h: succ a ≤ aFalsea: MyNat
h: succ a ≤ aFalsea, c: MyNat
h: a = succ a + c
introFalsea, c: MyNat
h: a = succ a + c
introFalsec: MyNat
h: zero = succ zero + c
intro.zeroFalsec: MyNat
h: zero = succ zero + c
intro.zeroFalsec: MyNat
h: zero = succ (zero + c)
intro.zeroFalsec: MyNat
h: zero = succ (zero + c)
intro.zeroFalsec: MyNat
h: zero = succ (zero + c)
intro.zeroFalseGoals accomplished! 🐙c, d: MyNat
hd: d = succ d + c → False
h: succ d = succ (succ d) + c
intro.succFalsec, d: MyNat
hd: d = succ d + c → False
h: succ d = succ (succ d) + c
intro.succFalsec, d: MyNat
hd: d = succ d + c → False
h: succ d = succ (succ d + c)
intro.succFalsec, d: MyNat
hd: d = succ d + c → False
h: succ d = succ (succ d + c)
intro.succFalsec, d: MyNat
hd: d = succ d + c → False
h: succ d = succ (succ d + c)
intro.succFalsec, d: MyNat
hd: d = succ d + c → False
h: succ d = succ (succ d + c)
intro.succd = succ d + cc, d: MyNat
hd: d = succ d + c → False
h: succ d = succ (succ d + c)
intro.succ.asucc d = succ (succ d + c)Goals accomplished! 🐙
Pro tip:
The conv
tactic allows you to perform targeted rewriting on a goal or hypothesis, by focusing
on particular subexpressions.
conv =>
lhs
rw hc
This is an incantation which rewrites hc
only on the left hand side of the goal.
You didn't need to use conv
in the above proof
but it's a helpful trick when rw
is rewriting too much.
For a deeper discussion on conv
see Conversion Tactic Mode
Next up Level 14
import MyNat.Definition
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import AdditionWorld.Level2 -- add_assoc
namespace MyNat
open MyNat
Inequality world.
Level 14: add_le_add_left
I know these are easy and we've done several already, but this is one
of the axioms for an ordered commutative monoid! The nature of formalizing
is that we should formalize all "obvious" lemmas, and then when we're
actually using ≤
in real life, everything will be there. Note also,
of course, that all of these lemmas are already formalized in Lean's
maths library already, for Lean's inbuilt natural numbers.
Lemma : add_le_add_left
If a ≤ b
then for all t
, t+a ≤ t+b
.
theoremadd_le_add_left {add_le_add_left: ∀ {a b : MyNat}, a ≤ b → ∀ (t : MyNat), t + a ≤ t + baa: MyNatb :b: MyNatMyNat} (MyNat: Typeh :h: a ≤ ba ≤a: MyNatb) (b: MyNatt :t: MyNatMyNat) :MyNat: Typet +t: MyNata ≤a: MyNatt +t: MyNatb :=b: MyNata, b: MyNat
h: a ≤ b
t: MyNatt + a ≤ t + ba, b: MyNat
h: a ≤ b
t: MyNatt + a ≤ t + ba, b, t, c: MyNat
hc: b = a + c
introt + a ≤ t + ba, b, t, c: MyNat
hc: b = a + c
introt + b = t + a + ca, b, t, c: MyNat
hc: b = a + c
introt + b = t + a + ca, b, t, c: MyNat
hc: b = a + c
introt + (a + c) = t + a + ca, b, t, c: MyNat
hc: b = a + c
introt + (a + c) = t + a + ca, b, t, c: MyNat
hc: b = a + c
introt + (a + c) = t + a + ca, b, t, c: MyNat
hc: b = a + c
introt + (a + c) = t + (a + c)Goals accomplished! 🐙
Next up Level 15
import MyNat.Definition
import MyNat.Addition -- add_zero
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import AdditionWorld.Level2 -- add_assoc
import AdditionWorld.Level3 -- succ_add
import InequalityWorld.Level2 -- le_refl
namespace MyNat
open MyNat
Inequality world.
Level 15: introducing <
To get the remaining collectibles in this world, we need to
give a definition of <
. By default, the definition of a < b
in Lean, once ≤
is defined, is this:
a < b := a ≤ b ∧ ¬ (b ≤ a)
But a much more usable definition would be this:
a < b := succ a ≤ b
Let's prove that these two definitions are the same
Lemma : lt_aux₁
For all naturals a
and b
, a ≤ b ∧ ¬(b ≤ a) ⟹ succ a ≤ b.
lemmalt_aux₁ (lt_aux₁: ∀ (a b : MyNat), a ≤ b ∧ ¬b ≤ a → succ a ≤ baa: MyNatb :b: MyNatMyNat) :MyNat: Typea ≤a: MyNatb ∧ ¬ (b: MyNatb ≤b: MyNata) →a: MyNatsuccsucc: MyNat → MyNata ≤a: MyNatb :=b: MyNata, b: MyNata ≤ b ∧ ¬b ≤ a → succ a ≤ ba, b: MyNat
h: a ≤ b ∧ ¬b ≤ asucc a ≤ ba, b: MyNat
h: a ≤ b ∧ ¬b ≤ asucc a ≤ ba, b: MyNat
h1: a ≤ b
h2: ¬b ≤ a
introsucc a ≤ ba, b: MyNat
h1: a ≤ b
h2: ¬b ≤ a
introsucc a ≤ ba, b: MyNat
h2: ¬b ≤ a
c: MyNat
hc: b = a + c
intro.introsucc a ≤ ba, b: MyNat
h2: ¬b ≤ a
c: MyNat
hc: b = a + c
intro.introsucc a ≤ ba, b: MyNat
h2: ¬b ≤ a
hc: b = a + zero
intro.intro.zerosucc a ≤ ba, b: MyNat
h2: ¬b ≤ a
hc: b = a + zero
intro.intro.zero.hFalsea, b: MyNat
h2: ¬b ≤ a
hc: b = a + zero
intro.intro.zero.hFalsea, b: MyNat
h2: ¬b ≤ a
hc: b = a + 0
intro.intro.zero.hFalsea, b: MyNat
h2: ¬b ≤ a
hc: b = a
intro.intro.zero.hFalsea, b: MyNat
h2: ¬b ≤ a
hc: b = a
intro.intro.zero.hFalsea, b: MyNat
h2: ¬b ≤ a
hc: b = a
intro.intro.zero.hFalsea, b: MyNat
h2: ¬b ≤ a
hc: b = a
intro.intro.zero.hb ≤ aa, b: MyNat
h2: ¬b ≤ a
hc: b = a
intro.intro.zero.hb ≤ aa, b: MyNat
h2: ¬b ≤ a
hc: b = a
intro.intro.zero.ha ≤ aGoals accomplished! 🐙a, b: MyNat
h2: ¬b ≤ a
d: MyNat
hc: b = a + succ d
intro.intro.succsucc a ≤ ba, b: MyNat
h2: ¬b ≤ a
d: MyNat
hc: b = a + succ d
intro.intro.succb = succ a + da, b: MyNat
h2: ¬b ≤ a
d: MyNat
hc: b = a + succ d
intro.intro.succb = succ a + da, b: MyNat
h2: ¬b ≤ a
d: MyNat
hc: b = a + succ d
intro.intro.succa + succ d = succ a + da, b: MyNat
h2: ¬b ≤ a
d: MyNat
hc: b = a + succ d
intro.intro.succa + succ d = succ a + da, b: MyNat
h2: ¬b ≤ a
d: MyNat
hc: b = a + succ d
intro.intro.succa + succ d = succ a + da, b: MyNat
h2: ¬b ≤ a
d: MyNat
hc: b = a + succ d
intro.intro.succsucc (a + d) = succ a + da, b: MyNat
h2: ¬b ≤ a
d: MyNat
hc: b = a + succ d
intro.intro.succsucc (a + d) = succ a + da, b: MyNat
h2: ¬b ≤ a
d: MyNat
hc: b = a + succ d
intro.intro.succsucc (a + d) = succ a + da, b: MyNat
h2: ¬b ≤ a
d: MyNat
hc: b = a + succ d
intro.intro.succsucc (a + d) = succ (a + d)Goals accomplished! 🐙
Next up Level 16
import MyNat.Definition
import MyNat.Addition -- add_zero
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import AdditionWorld.Level2 -- add_assoc
import AdditionWorld.Level3 -- succ_add
import InequalityWorld.Level5 -- le_trans
import InequalityWorld.Level6 -- le_antisymm
import InequalityWorld.Level10 -- le_succ_self
import AdvancedAdditionWorld.Level13 -- ne_succ_self
namespace MyNat
open MyNat
Inequality world.
Level 16: equivalence of two definitions of <
Now let's go the other way.
Lemma : lt_aux₂
For all naturals a
and b
, succ a ≤ b ⟹ a ≤ b ∧ ¬ (b ≤ a)
.
lemmalt_aux₂ (lt_aux₂: ∀ (a b : MyNat), succ a ≤ b → a ≤ b ∧ ¬b ≤ aaa: MyNatb :b: MyNatMyNat) :MyNat: Typesuccsucc: MyNat → MyNata ≤a: MyNatb →b: MyNata ≤a: MyNatb ∧ ¬ (b: MyNatb ≤b: MyNata) :=a: MyNata, b: MyNatsucc a ≤ b → a ≤ b ∧ ¬b ≤ aa, b: MyNat
h: succ a ≤ ba ≤ b ∧ ¬b ≤ aa, b: MyNat
h: succ a ≤ b
lefta ≤ ba, b: MyNat
h: succ a ≤ b¬b ≤ aa, b: MyNat
h: succ a ≤ b
lefta ≤ ba, b: MyNat
h: succ a ≤ b
left.haba ≤ succ aa, b: MyNat
h: succ a ≤ bsucc a ≤ ba, b: MyNat
h: succ a ≤ b
left.hbcsucc a ≤ bGoals accomplished! 🐙a, b: MyNat
h: succ a ≤ b
right¬b ≤ aa, b: MyNat
h: succ a ≤ b
right¬b ≤ aa, b: MyNat
h: succ a ≤ b
nh: b ≤ a
rightFalsea, b: MyNat
h: succ a ≤ b
nh: b ≤ a
righta = succ aa, b: MyNat
h: succ a ≤ b
nh: b ≤ a
right.haba ≤ succ aa, b: MyNat
h: succ a ≤ b
nh: b ≤ asucc a ≤ aa, b: MyNat
h: succ a ≤ b
nh: b ≤ a
right.hbasucc a ≤ aGoals accomplished! 🐙Goals accomplished! 🐙
Now for the payoff.
Next up Level 17
import MyNat.Definition
import MyNat.Addition -- add_zero
import MyNat.Inequality -- le_iff_exists_add
import Mathlib.Tactic.Use -- use tactic
import InequalityWorld.Level15 -- lt_aux₁
import InequalityWorld.Level16 -- lt_aux₂
import AdvancedAdditionWorld.Level13 -- ne_succ_self
namespace MyNat
open MyNat
Inequality world.
Level 17: definition of <
OK so we are going to define a < b
by a ≤ b ∧ ¬ (b ≤ a)
,
and given lt_aux_one a b
and lt_aux_two a b
it should now just
be a few lines to prove a < b ↔ succ a ≤ b
.
Lemma : lt_iff_succ_le
For all naturals a
and b
, a<b ↔ succ a ≤ b.
lemmalt_iff_succ_le (lt_iff_succ_le: ∀ (a b : MyNat), a < b ↔ succ a ≤ baa: MyNatb :b: MyNatMyNat) :MyNat: Typea <a: MyNatb ↔b: MyNatsuccsucc: MyNat → MyNata ≤a: MyNatb :=b: MyNata, b: MyNata < b ↔ succ a ≤ ba, b: MyNat
mpa < b → succ a ≤ ba, b: MyNatsucc a ≤ b → a < ba, b: MyNat
mprsucc a ≤ b → a < bGoals accomplished! 🐙
Sadly that is the end of all our nicely documented levels in this tutorial!
Interested in playing levels involving other kinds of mathematics? Look here for more ideas about what to do next.
Interested in learning more? Join us on the
Zulip Lean chat
and ask questions in the #new members
stream. Real names preferred. Be nice.
If you want to see a whole bunch of great examples see Level 18
import MyNat.Definition import MyNat.Power import MyNat.Inequality -- LE import InequalityWorld.Level4 -- zero_le import InequalityWorld.Level5 -- le_trans import InequalityWorld.Level17 -- lt, lt_iff_succ_le import MultiplicationWorld.Level4 -- mul_add import MultiplicationWorld.Level8 -- mul_comm namespace MyNat open MyNat lemmalt_irrefl (lt_irrefl: ∀ (a : MyNat), ¬a < aa :a: MyNatMyNat) : ¬ (MyNat: Typea <a: MyNata) :=a: MyNata: MyNat¬a < aa: MyNat
h: a < aFalsea: MyNat
h: a < aFalsea: MyNat
h1: a ≤ a
h2: ¬a ≤ a
introFalsea: MyNat
h1: a ≤ a
h2: ¬a ≤ a
introa ≤ alemmaGoals accomplished! 🐙ne_of_lt (ne_of_lt: ∀ (a b : MyNat), a < b → a ≠ baa: MyNatb :b: MyNatMyNat) :MyNat: Typea <a: MyNatb →b: MyNata ≠a: MyNatb :=b: MyNata, b: MyNata < b → a ≠ ba, b: MyNat
h: a < ba ≠ ba, b: MyNat
h: a < b
h1: a = bFalsea, b: MyNat
h: a < b
h1: a = bFalsea, b: MyNat
h1: a = b
h2: a ≤ b
h3: ¬b ≤ a
introFalsea, b: MyNat
h1: a = b
h2: a ≤ b
h3: ¬b ≤ a
introb ≤ aa, b: MyNat
h1: a = b
h2: a ≤ b
h3: ¬b ≤ a
introb ≤ aa, b: MyNat
h1: a = b
h2: a ≤ b
h3: ¬b ≤ a
introb ≤ btheoremGoals accomplished! 🐙not_lt_zero (not_lt_zero: ∀ (a : MyNat), ¬a < 0a :a: MyNatMyNat) : ¬(MyNat: Typea <a: MyNat0) :=0: MyNata: MyNat¬a < 0a: MyNat
h: a < 0Falsea: MyNat
h: a < 0Falsea: MyNat
ha: a ≤ 0
hna: ¬0 ≤ a
introFalsea: MyNat
ha: a ≤ 0
hna: ¬0 ≤ a
intro0 ≤ atheoremGoals accomplished! 🐙lt_of_lt_of_le (lt_of_lt_of_le: ∀ (a b c : MyNat), a < b → b ≤ c → a < caa: MyNatbb: MyNatc :c: MyNatMyNat) :MyNat: Typea <a: MyNatb →b: MyNatb ≤b: MyNatc →c: MyNata <a: MyNatc :=c: MyNata, b, c: MyNata < b → b ≤ c → a < ca, b, c: MyNat
hab: a < bb ≤ c → a < ca, b, c: MyNat
hab: a < b
hbc: b ≤ ca < ca, b, c: MyNat
hab: a < b
hbc: b ≤ ca < ca, b, c: MyNat
hab: succ a ≤ b
hbc: b ≤ csucc a ≤ ca, b, c: MyNat
hab: succ a ≤ b
hbc: b ≤ csucc a ≤ ca, b, c: MyNat
hab: succ a ≤ b
hbc: b ≤ csucc a ≤ ca, b, c: MyNat
hab: succ a ≤ b
hbc: b ≤ csucc a ≤ ca, b, c: MyNat
hab: succ a ≤ b
x: MyNat
hx: c = b + x
introsucc a ≤ ca, b, c: MyNat
hab: succ a ≤ b
x: MyNat
hx: c = b + x
introsucc a ≤ ca, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ ca, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ ca, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ b + xa, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ b + xa, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ b + xa, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ succ a + y + xa, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ succ a + y + xa, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a + y + x = succ a + (y + x)a, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a + y + x = succ a + (y + x)a, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a + (y + x) = succ a + (y + x)theoremGoals accomplished! 🐙lt_of_le_of_lt (lt_of_le_of_lt: ∀ (a b c : MyNat), a ≤ b → b < c → a < caa: MyNatbb: MyNatc :c: MyNatMyNat) :MyNat: Typea ≤a: MyNatb →b: MyNatb <b: MyNatc →c: MyNata <a: MyNatc :=c: MyNata, b, c: MyNata ≤ b → b < c → a < ca, b, c: MyNat
hab: a ≤ bb < c → a < ca, b, c: MyNat
hab: a ≤ b
hbc: b < ca < ca, b, c: MyNat
hab: a ≤ b
hbc: b < ca < ca, b, c: MyNat
hab: a ≤ b
hbc: succ b ≤ csucc a ≤ ca, b, c: MyNat
hab: a ≤ b
hbc: succ b ≤ csucc a ≤ ca, b, c: MyNat
hab: a ≤ b
hbc: succ b ≤ csucc a ≤ ca, b, c: MyNat
hab: a ≤ b
hbc: succ b ≤ csucc a ≤ ca, b, c: MyNat
hab: a ≤ b
x: MyNat
hx: c = succ b + x
introsucc a ≤ ca, b, c: MyNat
hab: a ≤ b
x: MyNat
hx: c = succ b + x
introsucc a ≤ ca, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc a ≤ ca, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc a ≤ ca, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc a ≤ succ b + xa, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc a ≤ succ b + xa, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc a ≤ succ b + xa, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc a ≤ succ (a + y) + xa, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc a ≤ succ (a + y) + xa, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc (a + y) + x = succ a + (y + x)a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc (a + y) + x = succ a + (y + x)a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc (a + y + x) = succ a + (y + x)a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc (a + y + x) = succ a + (y + x)a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc (a + y + x) = succ a + (y + x)a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc (a + y + x) = succ (a + (y + x))a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc (a + y + x) = succ (a + (y + x))a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc (a + y + x) = succ (a + (y + x))a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y
intro.introsucc (a + (y + x)) = succ (a + (y + x))theoremGoals accomplished! 🐙lt_trans (lt_trans: ∀ (a b c : MyNat), a < b → b < c → a < caa: MyNatbb: MyNatc :c: MyNatMyNat) :MyNat: Typea <a: MyNatb →b: MyNatb <b: MyNatc →c: MyNata <a: MyNatc :=c: MyNata, b, c: MyNata < b → b < c → a < ca, b, c: MyNat
hab: a < bb < c → a < ca, b, c: MyNat
hab: a < b
hbc: b < ca < ca, b, c: MyNat
hab: a < b
hbc: b < ca < ca, b, c: MyNat
hab: succ a ≤ b
hbc: succ b ≤ csucc a ≤ ca, b, c: MyNat
hab: succ a ≤ b
hbc: succ b ≤ csucc a ≤ ca, b, c: MyNat
hab: succ a ≤ b
hbc: succ b ≤ csucc a ≤ ca, b, c: MyNat
hab: succ a ≤ b
hbc: succ b ≤ csucc a ≤ ca, b, c: MyNat
hab: succ a ≤ b
x: MyNat
hx: c = succ b + x
introsucc a ≤ ca, b, c: MyNat
hab: succ a ≤ b
x: MyNat
hx: c = succ b + x
introsucc a ≤ ca, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ ca, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ ca, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ succ b + xa, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ succ b + xa, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ succ b + xa, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ succ (succ a + y) + xa, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introsucc a ≤ succ (succ a + y) + xa, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introsucc (succ a + y) + x = succ a + (y + x + 1)a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introsucc (succ a + y) + x = succ a + (y + x + 1)a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introsucc (succ a + y + x) = succ a + (y + x + 1)a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introsucc (succ (a + y) + x) = succ a + (y + x + 1)a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introsucc (succ (a + y + x)) = succ (a + (y + x + 1))a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introsucc (succ (a + y + x)) = succ (a + (y + x + 1))a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introa + y + x + 1 + 1 = a + (y + x + 1) + 1a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introa + y + x + 1 + 1 = a + (y + x + 1) + 1a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y
intro.introa + y + x + 1 + 1 = succ (a + (y + x + 1))theoremGoals accomplished! 🐙lt_iff_le_and_ne (lt_iff_le_and_ne: ∀ (a b : MyNat), a < b ↔ a ≤ b ∧ a ≠ baa: MyNatb :b: MyNatMyNat) :MyNat: Typea <a: MyNatb ↔b: MyNata ≤a: MyNatb ∧b: MyNata ≠a: MyNatb :=b: MyNata, b: MyNata < b ↔ a ≤ b ∧ a ≠ ba, b: MyNat
mpa < b → a ≤ b ∧ a ≠ ba, b: MyNata ≤ b ∧ a ≠ b → a < ba, b: MyNat
mpa < b → a ≤ b ∧ a ≠ ba, b: MyNat
h: a < b
mpa ≤ b ∧ a ≠ ba, b: MyNat
h: a < b
mpa ≤ b ∧ a ≠ ba, b: MyNat
h1: a ≤ b
h2: ¬b ≤ a
mp.introa ≤ b ∧ a ≠ ba, b: MyNat
h1: a ≤ b
h2: ¬b ≤ a
mp.intro.lefta ≤ ba, b: MyNat
h1: a ≤ b
h2: ¬b ≤ aa ≠ ba, b: MyNat
h1: a ≤ b
h2: ¬b ≤ a
mp.intro.righta ≠ ba, b: MyNat
h1: a ≤ b
h2: ¬b ≤ a
h: a = b
mp.intro.rightFalsea, b: MyNat
h1: a ≤ b
h2: ¬b ≤ a
h: a = b
mp.intro.rightb ≤ aa, b: MyNat
h1: a ≤ b
h2: ¬b ≤ a
h: a = b
mp.intro.rightb ≤ aa, b: MyNat
h1: a ≤ b
h2: ¬b ≤ a
h: a = b
mp.intro.rightb ≤ bGoals accomplished! 🐙a, b: MyNat
mpra ≤ b ∧ a ≠ b → a < ba, b: MyNat
mpra ≤ b ∧ a ≠ b → a < ba, b: MyNat
h: a ≤ b ∧ a ≠ b
mpra < ba, b: MyNat
h: a ≤ b ∧ a ≠ b
mpra < ba, b: MyNat
h1: a ≤ b
h2: a ≠ b
mpr.introa < ba, b: MyNat
h1: a ≤ b
h2: a ≠ b
mpr.intro.lefta ≤ ba, b: MyNat
h1: a ≤ b
h2: a ≠ b¬b ≤ aa, b: MyNat
h1: a ≤ b
h2: a ≠ b
mpr.intro.right¬b ≤ aa, b: MyNat
h1: a ≤ b
h2: a ≠ b
h: b ≤ a
mpr.intro.rightFalsea, b: MyNat
h1: a ≤ b
h2: a ≠ b
h: b ≤ a
mpr.intro.righta = bGoals accomplished! 🐙theoremGoals accomplished! 🐙lt_succ_self (lt_succ_self: ∀ (n : MyNat), n < succ nn :n: MyNatMyNat) :MyNat: Typen <n: MyNatsuccsucc: MyNat → MyNatn :=n: MyNatn: MyNatn < succ nn: MyNatn < succ nn: MyNatn ≤ succ n ∧ n ≠ succ nn: MyNatn ≤ succ n ∧ n ≠ succ nn: MyNat
leftn ≤ succ nn: MyNatn ≠ succ nn: MyNat
leftn ≤ succ nGoals accomplished! 🐙n: MyNat
rightn ≠ succ nn: MyNat
rightn ≠ succ nn: MyNat
h: n = succ n
rightFalseGoals accomplished! 🐙lemmaGoals accomplished! 🐙succ_le_succ_iff (succ_le_succ_iff: ∀ (m n : MyNat), succ m ≤ succ n ↔ m ≤ nmm: MyNatn :n: MyNatMyNat) :MyNat: Typesuccsucc: MyNat → MyNatm ≤m: MyNatsuccsucc: MyNat → MyNatn ↔n: MyNatm ≤m: MyNatn :=n: MyNatm, n: MyNatsucc m ≤ succ n ↔ m ≤ nm, n: MyNat
mpsucc m ≤ succ n → m ≤ nm, n: MyNatm ≤ n → succ m ≤ succ nm, n: MyNat
mpsucc m ≤ succ n → m ≤ nm, n: MyNat
h: succ m ≤ succ n
mpm ≤ nm, n: MyNat
h: succ m ≤ succ n
mpm ≤ nm, n, c: MyNat
hc: succ n = succ m + c
mp.introm ≤ nm, n, c: MyNat
hc: succ n = succ m + c
mp.intron = m + cm, n, c: MyNat
hc: succ n = succ m + c
mp.intro.asucc n = succ (m + c)m, n, c: MyNat
hc: succ n = succ m + c
mp.intro.asucc n = succ (m + c)m, n, c: MyNat
hc: succ n = succ m + c
mp.intro.asucc m + c = succ (m + c)m, n, c: MyNat
hc: succ n = succ m + c
mp.intro.asucc m + c = succ (m + c)m, n, c: MyNat
hc: succ n = succ m + c
mp.intro.asucc m + c = succ (m + c)m, n, c: MyNat
hc: succ n = succ m + c
mp.intro.asucc (m + c) = succ (m + c)Goals accomplished! 🐙m, n: MyNat
mprm ≤ n → succ m ≤ succ nm, n: MyNat
mprm ≤ n → succ m ≤ succ nm, n: MyNat
h: m ≤ n
mprsucc m ≤ succ nm, n: MyNat
h: m ≤ n
mprsucc m ≤ succ nm, n, c: MyNat
hc: n = m + c
mpr.introsucc m ≤ succ nm, n, c: MyNat
hc: n = m + c
mpr.introsucc n = succ m + cm, n, c: MyNat
hc: n = m + c
mpr.introsucc n = succ m + cm, n, c: MyNat
hc: n = m + c
mpr.introsucc (m + c) = succ m + cm, n, c: MyNat
hc: n = m + c
mpr.introsucc (m + c) = succ m + cm, n, c: MyNat
hc: n = m + c
mpr.introsucc (m + c) = succ m + cm, n, c: MyNat
hc: n = m + c
mpr.introsucc (m + c) = succ (m + c)Goals accomplished! 🐙lemmaGoals accomplished! 🐙lt_succ_iff_le (lt_succ_iff_le: ∀ (m n : MyNat), m < succ n ↔ m ≤ nmm: MyNatn :n: MyNatMyNat) :MyNat: Typem <m: MyNatsuccsucc: MyNat → MyNatn ↔n: MyNatm ≤m: MyNatn :=n: MyNatm, n: MyNatm < succ n ↔ m ≤ nm, n: MyNatm < succ n ↔ m ≤ nm, n: MyNatsucc m ≤ succ n ↔ m ≤ nm, n: MyNatsucc m ≤ succ n ↔ m ≤ nlemmaGoals accomplished! 🐙le_of_add_le_add_left (le_of_add_le_add_left: ∀ (a b c : MyNat), a + b ≤ a + c → b ≤ caa: MyNatbb: MyNatc :c: MyNatMyNat) :MyNat: Typea +a: MyNatb ≤b: MyNata +a: MyNatc →c: MyNatb ≤b: MyNatc :=c: MyNata, b, c: MyNata + b ≤ a + c → b ≤ ca, b, c: MyNat
h: a + b ≤ a + cb ≤ ca, b, c: MyNat
h: a + b ≤ a + cb ≤ ca, b, c, d: MyNat
hd: a + c = a + b + d
introb ≤ ca, b, c, d: MyNat
hd: a + c = a + b + d
introc = b + da, b, c, d: MyNat
hd: a + c = a + b + d
intro.aa + c = a + (b + d)a, b, c, d: MyNat
hd: a + c = a + b + d
intro.aa + c = a + (b + d)a, b, c, d: MyNat
hd: a + c = a + b + d
intro.aa + b + d = a + (b + d)a, b, c, d: MyNat
hd: a + c = a + b + d
intro.aa + b + d = a + (b + d)a, b, c, d: MyNat
hd: a + c = a + b + d
intro.aa + b + d = a + (b + d)a, b, c, d: MyNat
hd: a + c = a + b + d
intro.aa + (b + d) = a + (b + d)lemmaGoals accomplished! 🐙lt_of_add_lt_add_left (lt_of_add_lt_add_left: ∀ (a b c : MyNat), a + b < a + c → b < caa: MyNatbb: MyNatc :c: MyNatMyNat) :MyNat: Typea +a: MyNatb <b: MyNata +a: MyNatc →c: MyNatb <b: MyNatc :=c: MyNata, b, c: MyNata + b < a + c → b < ca, b, c: MyNata + b < a + c → b < ca, b, c: MyNatsucc (a + b) ≤ a + c → b < ca, b, c: MyNatsucc (a + b) ≤ a + c → b < ca, b, c: MyNatsucc (a + b) ≤ a + c → b < ca, b, c: MyNat
h: succ (a + b) ≤ a + csucc b ≤ ca, b, c: MyNat
h: succ (a + b) ≤ a + c
aa + succ b ≤ a + ca, b, c: MyNat
h: succ (a + b) ≤ a + c
aa + succ b ≤ a + ca, b, c: MyNat
h: succ (a + b) ≤ a + c
asucc (a + b) ≤ a + ca, b, c: MyNat
h: succ (a + b) ≤ a + c
asucc (a + b) ≤ a + clemmaGoals accomplished! 🐙add_lt_add_right (add_lt_add_right: ∀ (a b : MyNat), a < b → ∀ (c : MyNat), a + c < b + caa: MyNatb :b: MyNatMyNat) :MyNat: Typea <a: MyNatb → ∀b: MyNatc :c: MyNatMyNat,MyNat: Typea +a: MyNatc <c: MyNatb +b: MyNatc :=c: MyNata, b: MyNata < b → ∀ (c : MyNat), a + c < b + ca, b: MyNat
h: a < b∀ (c : MyNat), a + c < b + ca, b: MyNat
h: a < b
c: MyNata + c < b + ca, b: MyNat
h: a < b
c: MyNata + c < b + ca, b: MyNat
h: succ a ≤ b
c: MyNatsucc (a + c) ≤ b + ca, b: MyNat
h: succ a ≤ b
c: MyNatsucc (a + c) ≤ b + ca, b: MyNat
h: succ a ≤ b
c: MyNatsucc (a + c) ≤ b + ca, b: MyNat
h: succ a ≤ b
c: MyNatsucc (a + c) ≤ b + ca, b, c, d: MyNat
hd: b = succ a + d
introsucc (a + c) ≤ b + ca, b, c, d: MyNat
hd: b = succ a + d
introb + c = succ (a + c) + da, b, c, d: MyNat
hd: b = succ a + d
introb + c = succ (a + c) + da, b, c, d: MyNat
hd: b = succ a + d
introsucc a + d + c = succ (a + c) + da, b, c, d: MyNat
hd: b = succ a + d
introsucc a + d + c = succ (a + c) + da, b, c, d: MyNat
hd: b = succ a + d
introsucc a + d + c = succ (a + c) + da, b, c, d: MyNat
hd: b = succ a + d
introsucc a + d + c = succ (a + c) + da, b, c, d: MyNat
hd: b = succ a + d
introsucc (a + d + c) = succ (a + c + d)a, b, c, d: MyNat
hd: b = succ a + d
introsucc (a + d + c) = succ (a + c) + da, b, c, d: MyNat
hd: b = succ a + d
introsucc (a + d + c) = succ (a + c + d)a, b, c, d: MyNat
hd: b = succ a + d
introsucc (a + c + d) = succ (a + c + d)-- BUGBUG: collectibles -- and now we get three achievements! -- instance : ordered_comm_monoid MyNat := -- { add_le_add_left := λ _ _, add_le_add_left, -- lt_of_add_lt_add_left := lt_of_add_lt_add_left, -- ..MyNat.add_comm_monoid, ..MyNat.partial_order} -- instance : canonically_ordered_monoid MyNat := -- { le_iff_exists_add := le_iff_exists_add, -- bot := 0, -- bot_le := zero_le, -- ..MyNat.ordered_comm_monoid, -- } -- instance : ordered_cancel_comm_monoid MyNat := -- { add_left_cancel := add_left_cancel, -- add_right_cancel := add_right_cancel, -- le_of_add_le_add_left := le_of_add_le_add_left, -- ..MyNat.ordered_comm_monoid} defGoals accomplished! 🐙succ_lt_succ_iff (succ_lt_succ_iff: ∀ (a b : MyNat), succ a < succ b ↔ a < baa: MyNatb :b: MyNatMyNat) :MyNat: Typesuccsucc: MyNat → MyNata <a: MyNatsuccsucc: MyNat → MyNatb ↔b: MyNata <a: MyNatb :=b: MyNata, b: MyNatsucc a < succ b ↔ a < ba, b: MyNatsucc a < succ b ↔ a < ba, b: MyNatsucc a < succ b ↔ a < ba, b: MyNatsucc (succ a) ≤ succ b ↔ succ a ≤ ba, b: MyNatsucc (succ a) ≤ succ b ↔ a < b-- multiplication theoremGoals accomplished! 🐙mul_le_mul_of_nonneg_left (mul_le_mul_of_nonneg_left: ∀ (a b c : MyNat), a ≤ b → 0 ≤ c → c * a ≤ c * baa: MyNatbb: MyNatc :c: MyNatMyNat) :MyNat: Typea ≤a: MyNatb →b: MyNat0 ≤0: MyNatc →c: MyNatc *c: MyNata ≤a: MyNatc *c: MyNatb :=b: MyNata, b, c: MyNata ≤ b → 0 ≤ c → c * a ≤ c * ba, b, c: MyNat
hab: a ≤ b0 ≤ c → c * a ≤ c * ba, b, c: MyNat
hab: a ≤ b0 ≤ c → c * a ≤ c * ba, b, c: MyNat
hab: a ≤ b
h0: 0 ≤ cc * a ≤ c * ba, b, c: MyNat
hab: a ≤ b
h0: 0 ≤ cc * a ≤ c * ba, b, c: MyNat
h0: 0 ≤ c
d: MyNat
hd: b = a + d
introc * a ≤ c * ba, b, c: MyNat
h0: 0 ≤ c
d: MyNat
hd: b = a + d
introc * a ≤ c * ba, b, c: MyNat
h0: 0 ≤ c
d: MyNat
hd: b = a + d
introc * a ≤ c * (a + d)a, b, c: MyNat
h0: 0 ≤ c
d: MyNat
hd: b = a + d
introc * a ≤ c * (a + d)a, b, c: MyNat
h0: 0 ≤ c
d: MyNat
hd: b = a + d
introc * a ≤ c * (a + d)a, b, c: MyNat
h0: 0 ≤ c
d: MyNat
hd: b = a + d
introc * a ≤ c * a + c * da, b, c: MyNat
h0: 0 ≤ c
d: MyNat
hd: b = a + d
introc * a ≤ c * a + c * dtheoremGoals accomplished! 🐙mul_le_mul_of_nonneg_right (mul_le_mul_of_nonneg_right: ∀ (a b c : MyNat), a ≤ b → 0 ≤ c → a * c ≤ b * caa: MyNatbb: MyNatc :c: MyNatMyNat) :MyNat: Typea ≤a: MyNatb →b: MyNat0 ≤0: MyNatc →c: MyNata *a: MyNatc ≤c: MyNatb *b: MyNatc :=c: MyNata, b, c: MyNata ≤ b → 0 ≤ c → a * c ≤ b * ca, b, c: MyNat
hab: a ≤ b0 ≤ c → a * c ≤ b * ca, b, c: MyNat
hab: a ≤ b
h0: 0 ≤ ca * c ≤ b * ca, b, c: MyNat
hab: a ≤ b
h0: 0 ≤ ca * c ≤ b * ca, b, c: MyNat
hab: a ≤ b
h0: 0 ≤ cc * a ≤ b * ca, b, c: MyNat
hab: a ≤ b
h0: 0 ≤ cc * a ≤ b * ca, b, c: MyNat
hab: a ≤ b
h0: 0 ≤ cc * a ≤ b * ca, b, c: MyNat
hab: a ≤ b
h0: 0 ≤ cc * a ≤ c * ba, b, c: MyNat
hab: a ≤ b
h0: 0 ≤ cc * a ≤ c * ba, b, c: MyNat
hab: a ≤ b
h0: 0 ≤ c
aa ≤ ba, b, c: MyNat
hab: a ≤ b
h0: 0 ≤ c0 ≤ ca, b, c: MyNat
hab: a ≤ b
h0: 0 ≤ c
a0 ≤ ctheoremGoals accomplished! 🐙mul_lt_mul_of_pos_left (mul_lt_mul_of_pos_left: ∀ (a b c : MyNat), a < b → 0 < c → c * a < c * baa: MyNatbb: MyNatc :c: MyNatMyNat) :MyNat: Typea <a: MyNatb →b: MyNat0 <0: MyNatc →c: MyNatc *c: MyNata <a: MyNatc *c: MyNatb :=b: MyNata, b, c: MyNata < b → 0 < c → c * a < c * ba, b, c: MyNat
hab: a < b0 < c → c * a < c * ba, b, c: MyNat
hab: a < b
hc: 0 < cc * a < c * ba, b, c: MyNat
hab: a < b
hc: 0 < cc * a < c * ba, b: MyNat
hab: a < b
hc: 0 < zero
zerozero * a < zero * ba, b: MyNat
hab: a < b
hc: 0 < zero
zero.hFalseGoals accomplished! 🐙a, b: MyNat
hab: a < b
d: MyNat
hc: 0 < succ d
succsucc d * a < succ d * ba, b: MyNat
hab: a < b
d: MyNat
succsucc d * a < succ d * ba, b: MyNat
hab: a < b
d: MyNat
succsucc d * a < succ d * ba, b: MyNat
hab: a < b
succ.zerosucc zero * a < succ zero * ba, b: MyNat
hab: a < b
succ.zerosucc zero * a < succ zero * ba, b: MyNat
hab: a < b
succ.zerozero * a + a < succ zero * ba, b: MyNat
hab: a < b
succ.zero0 * a + a < succ 0 * ba, b: MyNat
hab: a < b
succ.zero0 + a < succ 0 * ba, b: MyNat
hab: a < b
succ.zero0 + a < succ 0 * ba, b: MyNat
hab: a < b
succ.zero0 + a < succ 0 * ba, b: MyNat
hab: a < b
succ.zeroa < succ 0 * ba, b: MyNat
hab: a < b
succ.zeroa < 0 * b + ba, b: MyNat
hab: a < b
succ.zeroa < 0 + ba, b: MyNat
hab: a < b
succ.zeroa < ba, b: MyNat
hab: a < b
succ.zeroa < bGoals accomplished! 🐙a, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
succ.succsucc (succ e) * a < succ (succ e) * ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
succ.succsucc (succ e) * a < succ (succ e) * ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
succ.succsucc e * a + a < succ (succ e) * ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
succ.succsucc e * a + a < succ (succ e) * ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
succ.succsucc e * a + a < succ (succ e) * ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
succ.succsucc e * a + a < succ e * b + ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
succ.succsucc e * a + a < succ e * b + ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
succ.succsucc e * a + a < succ e * b + ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * bsucc e * a + a < succ e * b + aGoals accomplished! 🐙a, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
h: succ e * a + a < succ e * b + a
succ.succsucc e * b + a < succ e * b + ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
h: succ e * a + a < succ e * b + a
succ.succsucc e * b + a < succ e * b + ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
h: succ e * a + a < succ e * b + a
succ.succa + succ e * b < succ e * b + ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
h: succ e * a + a < succ e * b + a
succ.succa + succ e * b < succ e * b + ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
h: succ e * a + a < succ e * b + a
succ.succa + succ e * b < succ e * b + ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
h: succ e * a + a < succ e * b + a
succ.succa + succ e * b < b + succ e * ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
h: succ e * a + a < succ e * b + a
succ.succa + succ e * b < b + succ e * ba, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b
h: succ e * a + a < succ e * b + a
succ.succ.aa < btheoremGoals accomplished! 🐙mul_lt_mul_of_pos_right (mul_lt_mul_of_pos_right: ∀ (a b c : MyNat), a < b → 0 < c → a * c < b * caa: MyNatbb: MyNatc :c: MyNatMyNat) :MyNat: Typea <a: MyNatb →b: MyNat0 <0: MyNatc →c: MyNata *a: MyNatc <c: MyNatb *b: MyNatc :=c: MyNata, b, c: MyNata < b → 0 < c → a * c < b * ca, b, c: MyNat
ha: a < b
h0: 0 < ca * c < b * ca, b, c: MyNat
ha: a < b
h0: 0 < ca * c < b * ca, b, c: MyNat
ha: a < b
h0: 0 < cc * a < b * ca, b, c: MyNat
ha: a < b
h0: 0 < cc * a < b * ca, b, c: MyNat
ha: a < b
h0: 0 < cc * a < b * ca, b, c: MyNat
ha: a < b
h0: 0 < cc * a < c * ba, b, c: MyNat
ha: a < b
h0: 0 < cc * a < c * ba, b, c: MyNat
ha: a < b
h0: 0 < c
aa < ba, b, c: MyNat
ha: a < b
h0: 0 < c0 < ca, b, c: MyNat
ha: a < b
h0: 0 < c
a0 < c-- BUGBUG todo -- And now another achievement! The naturals are an ordered semiring. -- instance : ordered_semiring MyNat := -- { mul_le_mul_of_nonneg_left := mul_le_mul_of_nonneg_left, -- mul_le_mul_of_nonneg_right := mul_le_mul_of_nonneg_right, -- mul_lt_mul_of_pos_left := mul_lt_mul_of_pos_left, -- mul_lt_mul_of_pos_right := mul_lt_mul_of_pos_right, -- ..MyNat.semiring, -- ..MyNat.ordered_cancel_comm_monoid -- } -- The Orderd semiring would give us this theorem, but we can do it manually instead. lemmaGoals accomplished! 🐙mul_le_mul {mul_le_mul: ∀ {a b c d : MyNat}, a ≤ c → b ≤ d → 0 ≤ b → 0 ≤ c → a * b ≤ c * daa: MyNatbb: MyNatcc: MyNatd :d: MyNatMyNat} (MyNat: Typehac :hac: a ≤ ca ≤a: MyNatc) (c: MyNathbd :hbd: b ≤ db ≤b: MyNatd) (d: MyNatnn_b :nn_b: 0 ≤ b0 ≤0: MyNatb) (b: MyNatnn_c :nn_c: 0 ≤ c0 ≤0: MyNatc) :c: MyNata *a: MyNatb ≤b: MyNatc *c: MyNatd :=d: MyNata, b, c, d: MyNat
hac: a ≤ c
hbd: b ≤ d
nn_b: 0 ≤ b
nn_c: 0 ≤ ca * b ≤ c * dlemmaGoals accomplished! 🐙le_mul (le_mul: ∀ (a b c d : MyNat), a ≤ b → c ≤ d → a * c ≤ b * daa: MyNatbb: MyNatcc: MyNatd :d: MyNatMyNat) :MyNat: Typea ≤a: MyNatb →b: MyNatc ≤c: MyNatd →d: MyNata *a: MyNatc ≤c: MyNatb *b: MyNatd :=d: MyNata, b, c, d: MyNata ≤ b → c ≤ d → a * c ≤ b * da, b, c, d: MyNat
hab: a ≤ b
hcd: c ≤ da * c ≤ b * da, b, c, d: MyNat
hab: a ≤ b
hcd: c ≤ da * c ≤ b * db, c, d: MyNat
hcd: c ≤ d
hab: zero ≤ b
zerozero * c ≤ b * db, c, d: MyNat
hcd: c ≤ d
hab: zero ≤ b
zerozero * c ≤ b * db, c, d: MyNat
hcd: c ≤ d
hab: zero ≤ b
zero0 * c ≤ b * db, c, d: MyNat
hcd: c ≤ d
hab: zero ≤ b
zero0 ≤ b * db, c, d: MyNat
hcd: c ≤ d
hab: zero ≤ b
zero0 ≤ b * dGoals accomplished! 🐙b, c, d: MyNat
hcd: c ≤ d
t: MyNat
Ht: t ≤ b → t * c ≤ b * d
hab: succ t ≤ b
succsucc t * c ≤ b * db, c, d: MyNat
hcd: c ≤ d
t: MyNat
Ht: t ≤ b → t * c ≤ b * d
hab: succ t ≤ b
succsucc t * c ≤ b * db, c, d: MyNat
hcd: c ≤ d
t: MyNat
Ht: t ≤ b → t * c ≤ b * d
hab: succ t ≤ b
succsucc t * c ≤ b * db, c, d: MyNat
hcd: c ≤ d
t: MyNat
Ht: t ≤ b → t * c ≤ b * d
hab: succ t ≤ b
succsucc t * c ≤ b * db, c, d: MyNat
hcd: c ≤ d
t: MyNat
Ht: t ≤ b → t * c ≤ b * d
hab: succ t ≤ b0 ≤ cGoals accomplished! 🐙b, c, d: MyNat
hcd: c ≤ d
t: MyNat
Ht: t ≤ b → t * c ≤ b * d
hab: succ t ≤ b
cz: 0 ≤ c
succsucc t * c ≤ b * db, c, d: MyNat
hcd: c ≤ d
t: MyNat
Ht: t ≤ b → t * c ≤ b * d
hab: succ t ≤ b
cz: 0 ≤ c0 ≤ bGoals accomplished! 🐙lemmaGoals accomplished! 🐙pow_le (pow_le: ∀ (m n a : MyNat), m ≤ n → m ^ a ≤ n ^ amm: MyNatnn: MyNata :a: MyNatMyNat) :MyNat: Typem ≤m: MyNatn →n: MyNatm ^m: MyNata ≤a: MyNatn ^n: MyNata :=a: MyNatm, n, a: MyNatm ≤ n → m ^ a ≤ n ^ am, n, a: MyNat
h: m ≤ nm ^ a ≤ n ^ am, n, a: MyNat
h: m ≤ nm ^ a ≤ n ^ am, n: MyNat
h: m ≤ n
zerom ^ zero ≤ n ^ zerom, n: MyNat
h: m ≤ n
zerom ^ zero ≤ n ^ zerom, n: MyNat
h: m ≤ n
zerom ^ 0 ≤ n ^ 0m, n: MyNat
h: m ≤ n
zero1 ≤ n ^ 0m, n: MyNat
h: m ≤ n
zero1 ≤ 1Goals accomplished! 🐙m, n: MyNat
h: m ≤ n
t: MyNat
Ht: m ^ t ≤ n ^ t
succm ^ succ t ≤ n ^ succ tm, n: MyNat
h: m ≤ n
t: MyNat
Ht: m ^ t ≤ n ^ t
succm ^ succ t ≤ n ^ succ tm, n: MyNat
h: m ≤ n
t: MyNat
Ht: m ^ t ≤ n ^ t
succm ^ t * m ≤ n ^ succ tm, n: MyNat
h: m ≤ n
t: MyNat
Ht: m ^ t ≤ n ^ t
succm ^ t * m ≤ n ^ t * nm, n: MyNat
h: m ≤ n
t: MyNat
Ht: m ^ t ≤ n ^ t
succm ^ t * m ≤ n ^ t * nm, n: MyNat
h: m ≤ n
t: MyNat
Ht: m ^ t ≤ n ^ t
succ.am ^ t ≤ n ^ tm, n: MyNat
h: m ≤ n
t: MyNat
Ht: m ^ t ≤ n ^ tm ≤ nm, n: MyNat
h: m ≤ n
t: MyNat
Ht: m ^ t ≤ n ^ t
succ.am ≤ nlemmaGoals accomplished! 🐙strong_induction_aux (strong_induction_aux: ∀ (P : MyNat → Prop), (∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m) → ∀ (n c : MyNat), c < n → P cP :P: MyNat → PropMyNat →MyNat: TypeProp) (Prop: TypeIH : ∀IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P mm :m: MyNatMyNat, (∀MyNat: Typeb :b: MyNatMyNat,MyNat: Typeb <b: MyNatm →m: MyNatPP: MyNat → Propb) →b: MyNatPP: MyNat → Propm) (m: MyNatn :n: MyNatMyNat) : ∀MyNat: Typec <c: MyNatn,n: MyNatPP: MyNat → Propc :=c: MyNatP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
n: MyNat∀ (c : MyNat), c < n → P cP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
n: MyNat∀ (c : MyNat), c < n → P cP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
zero∀ (c : MyNat), c < zero → P cP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
c: MyNat
zeroc < zero → P cP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
c: MyNat
hc: c < zero
zeroP cP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
c: MyNat
hc: c < zero
zero.hFalseP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
c: MyNat
zero.hc < zero → FalseGoals accomplished! 🐙P: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
d: MyNat
hd: ∀ (c : MyNat), c < d → P c
succ∀ (c : MyNat), c < succ d → P cP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
d: MyNat
hd: ∀ (c : MyNat), c < d → P c
e: MyNat
he: e < succ d
succP eP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
d: MyNat
hd: ∀ (c : MyNat), c < d → P c
e: MyNat
he: e < succ d
succP eP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
d: MyNat
hd: ∀ (c : MyNat), c < d → P c
e: MyNat
he: e ≤ d
succP eP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
d: MyNat
hd: ∀ (c : MyNat), c < d → P c
e: MyNat
he: e ≤ d
succP eP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
d: MyNat
hd: ∀ (c : MyNat), c < d → P c
e: MyNat
he: e ≤ d
succP eP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
d: MyNat
hd: ∀ (c : MyNat), c < d → P c
e: MyNat
he: e ≤ d
succ.a∀ (b : MyNat), b < e → P bP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
d: MyNat
hd: ∀ (c : MyNat), c < d → P c
e: MyNat
he: e ≤ d
b: MyNat
hb: b < e
succ.aP bP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
d: MyNat
hd: ∀ (c : MyNat), c < d → P c
e: MyNat
he: e ≤ d
b: MyNat
hb: b < e
succ.a.ab < dtheoremGoals accomplished! 🐙strong_induction (strong_induction: ∀ (P : MyNat → Prop), (∀ (m : MyNat), (∀ (d : MyNat), d < m → P d) → P m) → ∀ (n : MyNat), P nP :P: MyNat → PropMyNat →MyNat: TypeProp) (Prop: TypeIH : ∀IH: ∀ (m : MyNat), (∀ (d : MyNat), d < m → P d) → P mm :m: MyNatMyNat, (∀MyNat: Typed :d: MyNatMyNat,MyNat: Typed <d: MyNatm →m: MyNatPP: MyNat → Propd) →d: MyNatPP: MyNat → Propm) : ∀m: MyNatn,n: MyNatPP: MyNat → Propn :=n: MyNatP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (d : MyNat), d < m → P d) → P m∀ (n : MyNat), P nP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (d : MyNat), d < m → P d) → P m
n: MyNatP nP: MyNat → Prop
IH: ∀ (m : MyNat), (∀ (d : MyNat), d < m → P d) → P m
n: MyNat
an < succ nGoals accomplished! 🐙
import MyNat.Definition
Tactics
rfl
rfl
stands for "reflexivity", which is a fancy way of saying that it will prove any
goal of the form A = A. It doesn't matter how complicated A is.
rewrite
The rewrite
tactic is the way to "substitute in" the value of a variable. In general, if you have a
hypothesis of the form A = B
, and your goal mentions the left hand side A
somewhere, then the
rewrite
tactic will replace the A
in your goal with a B
.
rw
The rw
tactic is simply rewrite
followed by rfl
.
induction
The induction
tactic applies induction on inductive type to the main goal,
producing one goal for each constructor of the inductive type.
simp
The simp
tactic uses lemmas and hypotheses to simplify the main goal target or
non-dependent hypotheses.
exact
If you can explicitly see how to make an element of your goal set,
i.e. you have a formula for it, then you can just write exact <formula>
and this will close the goal.
intro
If your goal is ⊢ P → Q
then intro p
will introduce a new
hypothesis p : P
and change the goal to ⊢ Q
.
intros
The intros
tactic is like intro
only it can introduce multiple hypotheses at once.
have
have h : t := e
adds the hypothesis h : t
to the current goal if e
is a term
of type t
.
apply
apply e
tries to match the current goal against the conclusion of e
's type.
assumption
The assumption
tries to solve the goal using a
hypothesis of compatible type
constructor
The constructor
tactic tries to solve the goal using a
hypothesis of compatible type.
cases
Assuming x
is a variable in the local context with an inductive type,
cases x
splits the main goal, producing one goal for each constructor of the
inductive type, in which the target is replaced by a general instance of that constructor.
left/right
If ⊢ P ∨ Q
is your goal, then left
changes this goal to ⊢ P
, and right
changes it to ⊢ Q
.
exfalso
exfalso
converts a goal ⊢ tgt
into ⊢ False
by applying False.elim
.
contradiction
The contradiction
tactic closes the main goal if its hypotheses
are "trivially contradictory".
by_cases
by_cases h : P
does a cases split on whether P
is true or false.
use
use
is a tactic which works on goals of the form ⊢ ∃ c, P(c)
where
P(c)
is some proposition which depends on c
. You can specify one of
the values of c
that you would like to choose to proof thereby proving
the existance is true.
revert
revert x
is the opposite to intro x
.
tauto
The tauto
tactic (and its variant tauto!
) will close various logic
goals.
<;>
tac1 <;> tac2
runs tac1
on the main goal and tac2
on each produced goal,
concatenating all goals produced by tac2
.
import MyNat.Definition
Tactic : rfl
rfl
stands for "reflexivity", which is a fancy way of saying that it will prove any
goal of the form A = A. It doesn't matter how complicated A is.
This is supposed to be an extensible tactic and users can add their own support for new reflexive relations.
import MyNat.Definition
Tactic : rewrite
The rewrite
tactic is the way to "substitute in" the value of a variable. In general, if you have a
hypothesis of the form A = B
, and your goal mentions the left hand side A
somewhere, then the
rewrite
tactic will replace the A
in your goal with a B
.
rewrite
takes a list of hypotheses, and will try to apply each one in turn.
So rewrite [e]
applies identity e
as a rewrite rule to the target of the main goal.
You can also make rewrite apply the hypothesis in reverse direction by adding
a left arrow (← or <-) before the name of the hypothesis rewrite [←e]
.
If e
is a defined constant, then the equational theorems associated with e
are used.
This provides a convenient way to unfold e
.
rewrite [e₁, ..., eₙ]
applies the given rules sequentially.
rewrite [e] at l
rewrites e at location(s) l, where l is either * or a list of hypotheses in the
local context. In the latter case, a turnstile ⊢ or |- can also be used, to signify the target of the goal:
rewrite [e] at l ⊢
import MyNat.Definition
Tactic : rw
The rw
tactic is simply the rewrite
tactic followed by rfl
.
See rewrite for more details.
See rfl for more details.
import MyNat.Definition
Tactic : induction
Assuming x
is a variable in the local context with an inductive type, induction x
applies induction
on x
to the main goal, producing one goal for each constructor of the inductive type, in which the
target is replaced by a general instance of that constructor and an inductive hypothesis is added
for each recursive argument to the constructor. If the type of an element in the local context
depends on x
, that element is reverted and reintroduced afterward, so that the inductive hypothesis
incorporates that hypothesis as well.
For example, given n : Nat
and a goal with a hypothesis h : P n
and target Q n
, induction n
produces
one goal with hypothesis h : P 0
and target Q 0
, and one goal with hypotheses h : P (Nat.succ a)
and
ih₁ : P a → Q a
and target Q (Nat.succ a)
. Here the names a
and ih₁
are chosen automatically and are
not accessible. You can use with
to provide the variables names for each constructor.
induction e
, where e
is an expression instead of a variable, generalizes e
in the goal, and then
performs induction on the resulting variable. induction e using r
allows the user to specify the
principle of induction that should be used. Here r
should be a theorem whose result type must be of
the form C t
, where C
is a bound variable and t
is a (possibly empty) sequence of bound variables
induction e generalizing z₁ ... zₙ
, where z₁ ... zₙ
are variables in the local context, generalizes
over z₁ ... zₙ
before applying the induction but then introduces them in each goal. In other words,
the net effect is that each inductive hypothesis is generalized. Given x : Nat
,
induction x with | zero => tac₁ | succ x' ih => tac₂
uses tactic tac₁
for the zero
case, and tac₂
for the succ
case.
import MyNat.Definition
Tactic : simp
The simp
tactic uses lemmas and hypotheses to simplify the main goal target or
non-dependent hypotheses. It has many variants:
simp
simplifies the main goal target using lemmas tagged with the attribute[simp]
.simp [h₁, h₂, ..., hₙ]
simplifies the main goal target using the lemmas tagged with the attribute[simp]
and the givenhᵢ
's, where thehᵢ
's are expressions. If anhᵢ
is a defined constantf
, then the equational lemmas associated withf
are used. This provides a convenient way to unfoldf
.simp [*]
simplifies the main goal target using the lemmas tagged with the attribute[simp]
and all hypotheses.simp only [h₁, h₂, ..., hₙ]
is likesimp [h₁, h₂, ..., hₙ]
but does not use[simp]
lemmas.simp [-id₁, ..., -idₙ]
simplifies the main goal target using the lemmas tagged with the attribute[simp]
, but removes the ones namedidᵢ
.simp at h₁ h₂ ... hₙ
simplifies the hypothesesh₁ : T₁
...hₙ : Tₙ
. If the target or another hypothesis depends onhᵢ
, a new simplified hypothesishᵢ
is introduced, but the old one remains in the local context.simp at *
simplifies all the hypotheses and the target.simp [*] at *
simplifies target and all (propositional) hypotheses using the other hypotheses.
import Mathlib.Tactic.Relation.Symm
Tactic : symm
Summary
symm
turns goals of the form ⊢ A = B
to ⊢ B = A
.
This tactic is extensible, meaning you can add new @[symm]
attributes to things to teach symm
new tricks, like we
did with the simp
tactic. To teach it how to deal with
≠
we write this:
@[symm] def neqSymm: ∀ {α : Type} (a b : α), a ≠ b → b ≠ a
neqSymm {α: Type
α : Type: Type 1
Type} (a: α
a b: α
b: α: Type
α) : a: α
a ≠ b: α
b → b: α
b ≠ a: α
a := Ne.symm: ∀ {α : Type} {a b : α}, a ≠ b → b ≠ a
Ne.symm
Levels 9 to 13 introduce the last axiom of Peano, namely
that 0 ≠ succ a
. The proof of this is called zero_ne_succ a
.
zero_ne_succ (a : MyNat) : 0 ≠ succ a
We can simply use the symm
tactic to flip this goal into
succ a ≠ 0
which then matches our zero_ne_succ
axiom.
import MyNat.Definition
Tactic : repeat
repeat x
applies tactic x
to main goal. If the application succeeds,
the tactic is applied recursively to the generated subgoals until it eventually fails.
import MyNat.Definition
Tactic exact
exact e
closes the main goal if its target type matches that of e
.
This is essentially a short hand for:
have h2 := e
assumption
import MyNat.Definition
Tactic intro
If your goal is ⊢ P → Q
then intro p
will introduce a new
hypothesis p : P
and change the goal to ⊢ Q
.
You can introduces one or more hypotheses, optionally naming and/or pattern-matching them.
For each hypothesis to be introduced, the remaining main goal's target type must
be a let
or function type.
intro
by itself introduces one anonymous hypothesis, which can be accessed by e.g.assumption
.intro x y
introduces two hypotheses and names them. Individual hypotheses can be anonymized via_
, or matched against a pattern:-- ... ⊢ α × β → ... intro (a, b) -- ..., a : α, b : β ⊢ ...
- Alternatively,
intro
can be combined with pattern matching much likefun
:intro | n + 1, 0 => tac | ...
import MyNat.Definition
Tactic intros
The intros
tactic is like intro
only it can introduce multiple hypotheses at once.
Usage intros h₁ h₂ h₃ ...
.
import MyNat.Definition
Tactic have
have h : t := e
adds the hypothesis h : t
to the current goal if e
is a term
of type t
.
- If
t
is omitted, it will be inferred. - If
h
is omitted, the namethis
is used. - The variant
have pattern := e
is equivalent tomatch e with | pattern => _
, and it is convenient for types that have only one applicable constructor. For example, givenh : p ∧ q ∧ r
,have ⟨h₁, h₂, h₃⟩ := h
produces the hypothesesh₁ : p
,h₂ : q
, andh₃ : r
.
import MyNat.Definition
Tactic apply
apply e
tries to match the current goal against the conclusion of e
's type.
If it succeeds, then the tactic returns as many subgoals as the number of premises that
have not been fixed by type inference or type class resolution.
Non-dependent premises are added before dependent ones.
The apply
tactic uses higher-order pattern matching, type class resolution,
and first-order unification with dependent types.
For example, suppose you have the goal ⊢ Q
and you have the hypothesis
g : P → Q
then apply h
will construct the path backwards,
moving the goal from Q
to P
.
import MyNat.Definition
Tactic assumption
The assumption
tactic tries to solve the main goal using a hypothesis of compatible type, or else fails.
Note also the ‹t›
term notation, which is a shorthand for show t by assumption
.
import MyNat.Definition
Tactic constructor
If the main goal's target type is an inductive type, constructor
solves it with
the first matching constructor, or else fails.
For example, if the goal is ⊢ P ∧ Q
then it finds the matching constructor And.intro
which has 2 parameters, so it solves the goal with two sub-goals, namely ⊢ P
and ⊢ Q
.
import MyNat.Definition
Tactic cases
cases
is similar to induction
only it drops the inductive hypothesis.
Assuming x
is a variable in the local context with an inductive type,
cases x
splits the main goal, producing one goal for each constructor of the
inductive type, in which the target is replaced by a general instance of that constructor.
If the type of an element in the local context depends on x
,
that element is reverted and reintroduced afterward,
so that the case split affects that hypothesis as well.
cases
detects unreachable cases and closes them automatically.
For example, given n : Nat
and a goal with a hypothesis h : P n
and target Q n
,
cases n
produces one goal with hypothesis h : P 0
and target Q 0
,
and one goal with hypothesis h : P (Nat.succ a)
and target Q (Nat.succ a)
.
Here the name a
is chosen automatically and is not accessible.
You can use with
to provide the variables names for each constructor.
cases e
, wheree
is an expression instead of a variable, generalizese
in the goal, and then cases on the resulting variable.- Given
as : List α
,cases as with | nil => tac₁ | cons a as' => tac₂
, uses tactictac₁
for thenil
case, andtac₂
for thecons
case, anda
andas'
are used as names for the new variables introduced. cases h : e
, wheree
is a variable or an expression, performs cases one
as above, but also adds a hypothesish : e = ...
to each hypothesis, where...
is the constructor instance for that particular case.
import MyNat.Definition
Tactic left and right
The tactics left
and right
work on a goal which is a type with two constructors, the classic example
being P ∨ Q
. To prove P ∨ Q
it suffices to either prove P
or prove Q
, and once you know which one
you are going for you can change the goal with left or right to the appropriate choice.
If ⊢ P ∨ Q
is your goal, then left
changes this goal to ⊢ P
, and right
changes it to ⊢ Q
.
import MyNat.Definition
Tactic left and right
The tactics left
and right
work on a goal which is a type with two constructors, the classic example
being P ∨ Q
. To prove P ∨ Q
it suffices to either prove P
or prove Q
, and once you know which one
you are going for you can change the goal with left or right to the appropriate choice.
If ⊢ P ∨ Q
is your goal, then left
changes this goal to ⊢ P
, and right
changes it to ⊢ Q
.
import MyNat.Definition
Tactic exfalso
Summary
exfalso
changes your goal to False
.
Details
We know that False
implies P
for any proposition P
, and so if your goal is P
then you should be able to apply
False → P
and reduce your goal to False
. This
is what the exfalso
tactic does. The theorem that False → P
is called False.elim
so one can achieve the same effect with apply False.elim
.
You might think this is a step backwards, but if you have a hypothesis h : ¬ P
then after rw [not_iff_imp_false] at h,
you can apply h,
to make progress.
This tactic can also be used in a proof by contradiction, where the hypotheses are enough
to deduce a contradiction and the goal happens to be some random statement (possibly
a False one) which you just want to simplify to False
.
import MyNat.Definition
Tactic contradiction
The contradiction
tactic closes the main goal if its hypotheses
are "trivially contradictory".
Inductive type/family with no applicable constructors
example (h : False) : p := by contradiction
Injectivity of constructors
example (h : none = some true) : p := by contradiction --
Decidable false proposition
example (h : 2 + 2 = 3) : p := by contradiction
Contradictory hypotheses
example (h : p) (h' : ¬ p) : q := by contradiction
Other simple contradictions such as
example (x : Nat) (h : x ≠ x) : p := by contradiction
import MyNat.Definition
Tactic : by_cases
Summary
by_cases h : P
does a cases split on whether P
is true or false.
Details
Some logic goals cannot be proved with intro
and apply
and exact
.
The simplest example is the law of the excluded middle ¬ ¬ P → P
.
You can prove this using truth tables but not with intro
, apply
etc.
To do a truth table proof, the tactic by_cases h : P
will turn a goal of
⊢ ¬ ¬ P → P
into two goals
P : Prop,
h : P
⊢ ¬¬P → P
P : Prop,
h : ¬P
⊢ ¬¬P → P
Each of these can now be proved using intro
, apply
, exact
and exfalso
.
Remember though that in these simple logic cases, high-powered logic
tactics like cc
and tauto!
will just prove everything.
import MyNat.Definition
Tactic use
Summary
use
works on the goal. If your goal is ⊢ ∃ c : MyNat, 1 + x = x + c
then use 1
will turn the goal into ⊢ 1 + x = x + 1
, and the rather
more unwise use 0
will turn it into the impossible-to-prove
⊢ 1 + x = x + 0
.
Details
use
is a tactic which works on goals of the form ⊢ ∃ c, P(c)
where
P(c)
is some proposition which depends on c
. With a goal of this
form, use 0
will turn the goal into ⊢ P(0)
, use x + y
(assuming
x
and y
are natural numbers in your local context) will turn
the goal into P(x + y)
and so on.
import MyNat.Definition
Tactic : revert
Summary
revert x
is the opposite to intro x
.
Details
If the tactic state looks like this
P Q : Prop,
h : P
⊢ Q
then revert h
will change it to
P Q : Prop
⊢ P → Q
revert
also works with things like natural numbers: if
the tactic state looks like this
m : MyNat
⊢ m + 1 = succ m
then revert m
will turn it into
⊢ ∀ (m : MyNat), m + 1 = MyNat.succ m
import MyNat.Definition
Tactic : tauto
Summary
The tauto
tactic (and its variant tauto!
) will close various logic
goals.
Details
tauto
is an all-purpose logic tactic which will try to solve goals using pure
logical reasoning -- for example it will close the following goal:
P Q : Prop,
hP : P,
hQ : Q
⊢ P ∧ Q
tauto
is supposed to only use constructive logic, but its big brother tauto!
uses classical logic
and hence closes more goals.
import MyNat.Definition
Tactic <;>
tac1 <;> tac2
runs tac1
on the main goal and tac2
on each produced goal,
concatenating all goals produced by tac2
.
tac1 <;> (tac2; tac3)
runs tac1
on the main goal and
then runs tac2
followed by tac3
on every subgoal produced by tac1
.