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:

screen

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 the mathlib4 library so it is coming from the import Mathlib.Tactic.Basic that is included in MyNat.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.

lemma 
example1: ∀ (x y z : MyNat), x * y + z = x * y + z
example1
(
x: MyNat
x
y: MyNat
y
z: MyNat
z
:
MyNat: Type
MyNat
) :
x: MyNat
x
*
y: MyNat
y
+
z: MyNat
z
=
x: MyNat
x
*
y: MyNat
y
+
z: MyNat
z
:=
x, y, z: MyNat

x * y + z = x * y + z

Goals 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).

lemma 
example2: ∀ (x y : MyNat), y = x + 7 → 2 * y = 2 * (x + 7)
example2
(
x: MyNat
x
y: MyNat
y
:
MyNat: Type
MyNat
) (
h: y = x + 7
h
:
y: MyNat
y
=
x: MyNat
x
+
7: MyNat
7
) :
2: MyNat
2
*
y: MyNat
y
=
2: MyNat
2
* (
x: MyNat
x
+
7: MyNat
7
) :=
x, y: MyNat
h: y = x + 7

2 * y = 2 * (x + 7)
x, y: MyNat
h: y = x + 7

2 * y = 2 * (x + 7)
x, y: MyNat
h: y = x + 7

2 * (x + 7) = 2 * (x + 7)
x, y: MyNat
h: y = x + 7

2 * (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 the MyNat.zero.
  • a function succ : MyNat → MyNat, with succ n interpreted as "the number after n", or the successor of n.
  • 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.

lemma 
example3: ∀ (a b : MyNat), succ a = b → succ (succ a) = succ b
example3
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) (
h: succ a = b
h
: (
succ: MyNat → MyNat
succ
a: MyNat
a
) =
b: MyNat
b
) :
succ: MyNat → MyNat
succ
(
succ: MyNat → MyNat
succ
a: MyNat
a
) =
succ: MyNat → MyNat
succ
b: MyNat
b
:=
a, b: MyNat
h: succ a = b

succ (succ a) = succ b
a, b: MyNat
h: succ a = b

succ (succ a) = succ b
a, b: MyNat
h: succ a = b

succ b = succ b
a, b: MyNat
h: succ a = b

succ b = succ b

Goals 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`.

lemma 
add_succ_zero: ∀ (a : MyNat), a + succ 0 = succ a
add_succ_zero
(
a: MyNat
a
:
MyNat: Type
MyNat
) :
a: MyNat
a
+ (
succ: MyNat → MyNat
succ
0: MyNat
0
) =
succ: MyNat → MyNat
succ
a: MyNat
a
:=
a: MyNat

a + succ 0 = succ a
a: MyNat

a + succ 0 = succ a
a: MyNat

succ (a + 0) = succ a
a: MyNat

succ (a + 0) = succ a
a: MyNat

succ (a + 0) = succ a
a: MyNat

succ a = succ a
a: MyNat

succ a = succ a

Goals 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, with succ n interpreted as "the number after n".
  • 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 with rw [add_zero].
  • add_succ (a b : MyNat) : a + succ b = succ (a + b). Use with rw [add_succ].
  • The principle of mathematical induction. Use with induction (see below)

Tactics:

  • rfl : proves goals of the form X = X
  • rw [h] : if h is a proof of A = 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.

lemma 
zero_add: ∀ (n : MyNat), 0 + n = n
zero_add
(
n: MyNat
n
:
MyNat: Type
MyNat
) :
0: MyNat
0
+
n: MyNat
n
=
n: MyNat
n
:=
n: MyNat

0 + n = n
n: MyNat

0 + n = n

zero
0 + zero = zero

Goals accomplished! 🐙
n: MyNat
ih: 0 + n = n

succ
0 + succ n = succ n
n: MyNat
ih: 0 + n = n

succ
0 + succ n = succ n
n: MyNat
ih: 0 + n = n

succ
succ (0 + n) = succ n
n: MyNat
ih: 0 + n = n

succ
succ (0 + n) = succ n
n: MyNat
ih: 0 + n = n

succ
succ (0 + n) = succ n
n: MyNat
ih: 0 + n = n

succ
succ n = succ n

Goals 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).

lemma 
add_assoc: ∀ (a b c : MyNat), a + b + c = a + (b + c)
add_assoc
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) : (
a: MyNat
a
+
b: MyNat
b
) +
c: MyNat
c
=
a: MyNat
a
+ (
b: MyNat
b
+
c: MyNat
c
) :=
a, b, c: MyNat

a + b + c = a + (b + c)
a, b, c: MyNat

a + b + c = a + (b + c)
a, b: MyNat

zero
a + b + zero = a + (b + zero)
a, b: MyNat

zero
a + b + zero = a + (b + zero)
a, b: MyNat

zero
a + b + 0 = a + (b + 0)
a, b: MyNat

zero
a + b + 0 = a + (b + 0)
a, b: MyNat

zero
a + b + 0 = a + (b + 0)
a, b: MyNat

zero
a + b = a + (b + 0)
a, b: MyNat

zero
a + b = a + (b + 0)
a, b: MyNat

zero
a + b = a + (b + 0)
a, b: MyNat

zero
a + b = a + b

Goals accomplished! 🐙
a, b, c: MyNat
ih: a + b + c = a + (b + c)

succ
a + b + succ c = a + (b + succ c)
a, b, c: MyNat
ih: a + b + c = a + (b + c)

succ
a + b + succ c = a + (b + succ c)
a, b, c: MyNat
ih: a + b + c = a + (b + c)

succ
succ (a + b + c) = a + (b + succ c)
a, b, c: MyNat
ih: a + b + c = a + (b + c)

succ
succ (a + b + c) = a + (b + succ c)
a, b, c: MyNat
ih: a + b + c = a + (b + c)

succ
succ (a + b + c) = a + (b + succ c)
a, b, c: MyNat
ih: a + b + c = a + (b + c)

succ
succ (a + b + c) = a + succ (b + c)
a, b, c: MyNat
ih: a + b + c = a + (b + c)

succ
succ (a + b + c) = a + succ (b + c)
a, b, c: MyNat
ih: a + b + c = a + (b + c)

succ
succ (a + b + c) = a + succ (b + c)
a, b, c: MyNat
ih: a + b + c = a + (b + c)

succ
succ (a + b + c) = succ (a + (b + c))
a, b, c: MyNat
ih: a + b + c = a + (b + c)

succ
succ (a + b + c) = succ (a + (b + c))
a, b, c: MyNat
ih: a + b + c = a + (b + c)

succ
succ (a + b + c) = succ (a + (b + c))
a, b, c: MyNat
ih: a + b + c = a + (b + c)

succ
succ (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).

lemma 
succ_add: ∀ (a b : MyNat), succ a + b = succ (a + b)
succ_add
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
succ: MyNat → MyNat
succ
a: MyNat
a
+
b: MyNat
b
=
succ: MyNat → MyNat
succ
(
a: MyNat
a
+
b: MyNat
b
) :=
a, b: MyNat

succ a + b = succ (a + b)
a, b: MyNat

succ a + b = succ (a + b)
a: MyNat

zero
succ a + zero = succ (a + zero)

Goals accomplished! 🐙
a, b: MyNat
ih: succ a + b = succ (a + b)

succ
succ a + succ b = succ (a + succ b)
a, b: MyNat
ih: succ a + b = succ (a + b)

succ
succ a + succ b = succ (a + succ b)
a, b: MyNat
ih: succ a + b = succ (a + b)

succ
succ (succ a + b) = succ (a + succ b)
a, b: MyNat
ih: succ a + b = succ (a + b)

succ
succ (succ a + b) = succ (a + succ b)
a, b: MyNat
ih: succ a + b = succ (a + b)

succ
succ (succ a + b) = succ (a + succ b)
a, b: MyNat
ih: succ a + b = succ (a + b)

succ
succ (succ (a + b)) = succ (a + succ b)
a, b: MyNat
ih: succ a + b = succ (a + b)

succ
succ (succ (a + b)) = succ (a + succ b)
a, b: MyNat
ih: succ a + b = succ (a + b)

succ
succ (succ (a + b)) = succ (a + succ b)
a, b: MyNat
ih: succ a + b = succ (a + b)

succ
succ (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.

lemma 
add_comm: ∀ (a b : MyNat), a + b = b + a
add_comm
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
a: MyNat
a
+
b: MyNat
b
=
b: MyNat
b
+
a: MyNat
a
:=
a, b: MyNat

a + b = b + a
a, b: MyNat

a + b = b + a
a: MyNat

zero
a + zero = zero + a
a: MyNat

zero
a + zero = zero + a
a: MyNat

zero
a + 0 = 0 + a
a: MyNat

zero
a + 0 = 0 + a
a: MyNat

zero
a + 0 = 0 + a
a: MyNat

zero
a = 0 + a
a: MyNat

zero
a = 0 + a
a: MyNat

zero
a = 0 + a
a: MyNat

zero
a = a

Goals accomplished! 🐙
a, b: MyNat
ih: a + b = b + a

succ
a + succ b = succ b + a
a, b: MyNat
ih: a + b = b + a

succ
a + succ b = succ b + a
a, b: MyNat
ih: a + b = b + a

succ
succ (a + b) = succ b + a
a, b: MyNat
ih: a + b = b + a

succ
succ (a + b) = succ b + a
a, b: MyNat
ih: a + b = b + a

succ
succ (a + b) = succ b + a
a, b: MyNat
ih: a + b = b + a

succ
succ (b + a) = succ b + a
a, b: MyNat
ih: a + b = b + a

succ
succ (b + a) = succ b + a
a, b: MyNat
ih: a + b = b + a

succ
succ (b + a) = succ b + a
a, b: MyNat
ih: a + b = b + a

succ
succ (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.

theorem 
succ_eq_add_one: ∀ (n : MyNat), succ n = n + 1
succ_eq_add_one
(
n: MyNat
n
:
MyNat: Type
MyNat
) :
succ: MyNat → MyNat
succ
n: MyNat
n
=
n: MyNat
n
+
1: MyNat
1
:=
n: MyNat

succ n = n + 1
n: MyNat

succ n = n + 1
n: MyNat

succ n = n + succ 0
n: MyNat

succ n = n + succ 0
n: MyNat

succ n = n + succ 0
n: MyNat

succ n = succ (n + 0)
n: MyNat

succ 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.

lemma 
add_right_comm: ∀ (a b c : MyNat), a + b + c = a + c + b
add_right_comm
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) :
a: MyNat
a
+
b: MyNat
b
+
c: MyNat
c
=
a: MyNat
a
+
c: MyNat
c
+
b: MyNat
b
:=
a, b, c: MyNat

a + b + c = a + c + b
a, b, c: MyNat

a + b + c = a + c + b
a, b, c: MyNat

a + (b + c) = a + c + b
a, b, c: MyNat

a + (b + c) = a + c + b
a, b, c: MyNat

a + (b + c) = a + c + b
a, b, c: MyNat

a + (c + b) = a + c + b
a, b, c: MyNat

a + (c + b) = a + c + b
a, b, c: MyNat

a + (c + b) = a + c + b
a, b, c: MyNat

a + c + b = a + c + b

Goals 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:

lemma 
add_left_comm: ∀ (a b c : MyNat), a + (b + c) = b + (a + c)
add_left_comm
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) :
a: MyNat
a
+ (
b: MyNat
b
+
c: MyNat
c
) =
b: MyNat
b
+ (
a: MyNat
a
+
c: MyNat
c
) :=
a, b, c: MyNat

a + (b + c) = b + (a + c)
a, b, c: MyNat

a + (b + c) = b + (a + c)
a, b, c: MyNat

a + b + c = b + (a + c)
a, b, c: MyNat

a + b + c = b + (a + c)
a, b, c: MyNat

a + b + c = b + (a + c)
a, b, c: MyNat

b + a + c = b + (a + c)
a, b, c: MyNat

b + a + c = b + (a + c)
a, b, c: MyNat

b + a + c = b + (a + c)
a, b, c: MyNat

b + (a + c) = b + (a + c)

Goals accomplished! 🐙
attribute [simp]
add_assoc: ∀ (a b c : MyNat), a + b + c = a + (b + c)
add_assoc
add_comm: ∀ (a b : MyNat), a + b = b + a
add_comm
add_left_comm: ∀ (a b c : MyNat), a + (b + c) = b + (a + c)
add_left_comm
example: ∀ (a b c d e : MyNat), a + b + c + d + e = c + (b + e + a) + d
example
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
d: MyNat
d
e: MyNat
e
:
MyNat: Type
MyNat
) : (((
a: MyNat
a
+
b: MyNat
b
)+
c: MyNat
c
)+
d: MyNat
d
)+
e: MyNat
e
=(
c: MyNat
c
+((
b: MyNat
b
+
e: MyNat
e
)+
a: MyNat
a
))+
d: MyNat
d
:=
a, b, c, d, e: MyNat

a + b + c + d + e = c + (b + e + a) + d

Goals 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.

lemma 
zero_mul: ∀ (m : MyNat), 0 * m = 0
zero_mul
(
m: MyNat
m
:
MyNat: Type
MyNat
) :
0: MyNat
0
*
m: MyNat
m
=
0: MyNat
0
:=
m: MyNat

0 * m = 0
m: MyNat

0 * m = 0

zero
0 * zero = 0

zero
0 * zero = 0

zero
0 * 0 = 0

zero
0 * 0 = 0

zero
0 * 0 = 0

zero
0 = 0

Goals accomplished! 🐙
m: MyNat
ih: 0 * m = 0

succ
0 * succ m = 0
m: MyNat
ih: 0 * m = 0

succ
0 * succ m = 0
m: MyNat
ih: 0 * m = 0

succ
0 * m + 0 = 0
m: MyNat
ih: 0 * m = 0

succ
0 * m + 0 = 0
m: MyNat
ih: 0 * m = 0

succ
0 * m + 0 = 0
m: MyNat
ih: 0 * m = 0

succ
0 + 0 = 0
m: MyNat
ih: 0 * m = 0

succ
0 + 0 = 0
m: MyNat
ih: 0 * m = 0

succ
0 + 0 = 0
m: MyNat
ih: 0 * m = 0

succ
0 = 0

Goals 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.

lemma 
mul_one: ∀ (m : MyNat), m * 1 = m
mul_one
(
m: MyNat
m
:
MyNat: Type
MyNat
) :
m: MyNat
m
*
1: MyNat
1
=
m: MyNat
m
:=
m: MyNat

m * 1 = m
m: MyNat

m * 1 = m
m: MyNat

m * succ 0 = m
m: MyNat

m * succ 0 = m
m: MyNat

m * succ 0 = m
m: MyNat

m * 0 + m = m
m: MyNat

m * 0 + m = m
m: MyNat

m * 0 + m = m
m: MyNat

0 + m = m
m: MyNat

0 + m = m
m: MyNat

0 + m = m
m: MyNat

m = m

Goals 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.

lemma 
one_mul: ∀ (m : MyNat), 1 * m = m
one_mul
(
m: MyNat
m
:
MyNat: Type
MyNat
) :
1: MyNat
1
*
m: MyNat
m
=
m: MyNat
m
:=
m: MyNat

1 * m = m
m: MyNat

1 * m = m

zero
1 * zero = zero

zero
1 * zero = zero

zero
1 * 0 = 0

zero
1 * 0 = 0

zero
1 * 0 = 0

zero
0 = 0

Goals accomplished! 🐙
m: MyNat
ih: 1 * m = m

succ
1 * succ m = succ m
m: MyNat
ih: 1 * m = m

succ
1 * succ m = succ m
m: MyNat
ih: 1 * m = m

succ
1 * m + 1 = succ m
m: MyNat
ih: 1 * m = m

succ
1 * m + 1 = succ m
m: MyNat
ih: 1 * m = m

succ
1 * m + 1 = succ m
m: MyNat
ih: 1 * m = m

succ
m + 1 = succ m
m: MyNat
ih: 1 * m = m

succ
m + 1 = succ m
m: MyNat
ih: 1 * m = m

succ
m + 1 = succ m
m: MyNat
ih: 1 * m = m

succ
m + 1 = m + 1

Goals 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.

lemma 
mul_add: ∀ (t a b : MyNat), t * (a + b) = t * a + t * b
mul_add
(
t: MyNat
t
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
t: MyNat
t
* (
a: MyNat
a
+
b: MyNat
b
) =
t: MyNat
t
*
a: MyNat
a
+
t: MyNat
t
*
b: MyNat
b
:=
t, a, b: MyNat

t * (a + b) = t * a + t * b
t, a, b: MyNat

t * (a + b) = t * a + t * b
t, a: MyNat

zero
t * (a + zero) = t * a + t * zero
t, a: MyNat

zero
t * (a + zero) = t * a + t * zero
t, a: MyNat

zero
t * (a + 0) = t * a + t * 0
t, a: MyNat

zero
t * a = t * a + t * 0
t, a: MyNat

zero
t * a = t * a + 0
t, a: MyNat

zero
t * a = t * a

Goals accomplished! 🐙
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * (a + succ b) = t * a + t * succ b
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * (a + succ b) = t * a + t * succ b
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * succ (a + b) = t * a + t * succ b
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * succ (a + b) = t * a + t * succ b
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * succ (a + b) = t * a + t * succ b
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * (a + b) + t = t * a + t * succ b
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * (a + b) + t = t * a + t * succ b
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * (a + b) + t = t * a + t * succ b
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * a + t * b + t = t * a + t * succ b
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * a + t * b + t = t * a + t * succ b
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * a + t * b + t = t * a + t * succ b
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * a + t * b + t = t * a + (t * b + t)
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * a + t * b + t = t * a + (t * b + t)
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * a + t * b + t = t * a + (t * b + t)
t, a, b: MyNat
ih: t * (a + b) = t * a + t * b

succ
t * a + (t * b + t) = t * a + (t * b + t)

Goals accomplished! 🐙
def
left_distrib: ∀ (t a b : MyNat), t * (a + b) = t * a + t * b
left_distrib
:=
mul_add: ∀ (t a b : MyNat), t * (a + b) = t * a + t * b
mul_add
-- the "proper" name for this lemma

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).

lemma 
mul_assoc: ∀ (a b c : MyNat), a * b * c = a * (b * c)
mul_assoc
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) : (
a: MyNat
a
*
b: MyNat
b
) *
c: MyNat
c
=
a: MyNat
a
* (
b: MyNat
b
*
c: MyNat
c
) :=
a, b, c: MyNat

a * b * c = a * (b * c)
a, b, c: MyNat

a * b * c = a * (b * c)
a, b: MyNat

zero
a * b * zero = a * (b * zero)
a, b: MyNat

zero
a * b * zero = a * (b * zero)
a, b: MyNat

zero
a * b * 0 = a * (b * 0)
a, b: MyNat

zero
a * b * 0 = a * (b * 0)
a, b: MyNat

zero
a * b * 0 = a * (b * 0)
a, b: MyNat

zero
a * b * 0 = a * (b * 0)

Goals accomplished! 🐙
a, b: MyNat

zero
0 = a * 0
a, b, c: MyNat
ih: a * b * c = a * (b * c)

succ
a * b * succ c = a * (b * succ c)
a, b, c: MyNat
ih: a * b * c = a * (b * c)

succ
a * b * succ c = a * (b * succ c)
a, b, c: MyNat
ih: a * b * c = a * (b * c)

succ
a * b * c + a * b = a * (b * succ c)
a, b, c: MyNat
ih: a * b * c = a * (b * c)

succ
a * b * c + a * b = a * (b * succ c)
a, b, c: MyNat
ih: a * b * c = a * (b * c)

succ
a * b * c + a * b = a * (b * succ c)
a, b, c: MyNat
ih: a * b * c = a * (b * c)

succ
a * b * c + a * b = a * (b * c + b)
a, b, c: MyNat
ih: a * b * c = a * (b * c)

succ
a * b * c + a * b = a * (b * c + b)
a, b, c: MyNat
ih: a * b * c = a * (b * c)

succ
a * b * c + a * b = a * (b * c + b)
a, b, c: MyNat
ih: a * b * c = a * (b * c)

succ
a * (b * c) + a * b = a * (b * c + b)
a, b, c: MyNat
ih: a * b * c = a * (b * c)

succ
a * (b * c) + a * b = a * (b * c + b)
a, b, c: MyNat
ih: a * b * c = a * (b * c)

succ
a * (b * c) + a * b = a * (b * c + b)
a, b, c: MyNat
ih: a * b * c = a * (b * c)

succ
a * (b * c) + a * b = a * (b * c) + a * b

Goals 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.

lemma 
succ_mul: ∀ (a b : MyNat), succ a * b = a * b + b
succ_mul
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
succ: MyNat → MyNat
succ
a: MyNat
a
*
b: MyNat
b
=
a: MyNat
a
*
b: MyNat
b
+
b: MyNat
b
:=
a, b: MyNat

succ a * b = a * b + b
a, b: MyNat

succ a * b = a * b + b
a: MyNat

zero
succ a * zero = a * zero + zero
a: MyNat

zero
succ a * zero = a * zero + zero
a: MyNat

zero
succ a * 0 = a * 0 + 0
a: MyNat

zero
succ a * 0 = a * 0 + 0
a: MyNat

zero
succ a * 0 = a * 0 + 0
a: MyNat

zero
0 = a * 0 + 0
a: MyNat

zero
0 = a * 0 + 0
a: MyNat

zero
0 = a * 0 + 0
a: MyNat

zero
0 = 0 + 0
a: MyNat

zero
0 = 0 + 0
a: MyNat

zero
0 = 0 + 0
a: MyNat

zero
0 = 0

Goals accomplished! 🐙
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ a * succ b = a * succ b + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ a * succ b = a * succ b + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ a * b + succ a = a * succ b + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ a * b + succ a = a * succ b + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ a * b + succ a = a * succ b + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ a * b + succ a = a * b + a + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ a * b + succ a = a * b + a + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ a * b + succ a = a * b + a + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
a * b + b + succ a = a * b + a + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
a * b + b + succ a = a * b + a + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
a * b + b + succ a = a * b + a + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ (a * b + b + a) = a * b + a + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ (a * b + b + a) = a * b + a + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ (a * b + b + a) = a * b + a + succ b
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ (a * b + b + a) = succ (a * b + a + b)
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ (a * b + b + a) = succ (a * b + a + b)
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ (a * b + b + a) = succ (a * b + a + b)
a, b: MyNat
ih: succ a * b = a * b + b

succ
succ (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.

lemma 
add_mul: ∀ (a b t : MyNat), (a + b) * t = a * t + b * t
add_mul
(
a: MyNat
a
b: MyNat
b
t: MyNat
t
:
MyNat: Type
MyNat
) : (
a: MyNat
a
+
b: MyNat
b
) *
t: MyNat
t
=
a: MyNat
a
*
t: MyNat
t
+
b: MyNat
b
*
t: MyNat
t
:=
a, b, t: MyNat

(a + b) * t = a * t + b * t
a, b, t: MyNat

(a + b) * t = a * t + b * t
a, t: MyNat

zero
(a + zero) * t = a * t + zero * t
a, t: MyNat

zero
(a + zero) * t = a * t + zero * t
a, t: MyNat

zero
(a + 0) * t = a * t + 0 * t
a, t: MyNat

zero
(a + 0) * t = a * t + 0 * t
a, t: MyNat

zero
(a + 0) * t = a * t + 0 * t
a, t: MyNat

zero
(a + 0) * t = a * t + 0
a, t: MyNat

zero
(a + 0) * t = a * t + 0
a, t: MyNat

zero
(a + 0) * t = a * t + 0
a, t: MyNat

zero
a * t = a * t + 0
a, t: MyNat

zero
a * t = a * t + 0
a, t: MyNat

zero
a * t = a * t + 0
a, t: MyNat

zero
a * t = a * t

Goals accomplished! 🐙
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
(a + succ b) * t = a * t + succ b * t
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
(a + succ b) * t = a * t + succ b * t
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
succ (a + b) * t = a * t + succ b * t
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
succ (a + b) * t = a * t + succ b * t
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
succ (a + b) * t = a * t + succ b * t
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
(a + b) * t + t = a * t + succ b * t
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
(a + b) * t + t = a * t + succ b * t
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
(a + b) * t + t = a * t + succ b * t
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
a * t + b * t + t = a * t + succ b * t
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
a * t + b * t + t = a * t + succ b * t
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
a * t + b * t + t = a * t + succ b * t
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
a * t + b * t + t = a * t + (b * t + t)
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
a * t + b * t + t = a * t + (b * t + t)
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
a * t + b * t + t = a * t + (b * t + t)
a, t, b: MyNat
ih: (a + b) * t = a * t + b * t

succ
a * 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.

lemma 
mul_comm: ∀ (a b : MyNat), a * b = b * a
mul_comm
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
a: MyNat
a
*
b: MyNat
b
=
b: MyNat
b
*
a: MyNat
a
:=
a, b: MyNat

a * b = b * a
a, b: MyNat

a * b = b * a
a: MyNat

zero
a * zero = zero * a
a: MyNat

zero
a * zero = zero * a
a: MyNat

zero
a * 0 = 0 * a
a: MyNat

zero
a * 0 = 0 * a
a: MyNat

zero
a * 0 = 0 * a
a: MyNat

zero
a * 0 = 0
a: MyNat

zero
a * 0 = 0
a: MyNat

zero
a * 0 = 0
a: MyNat

zero
0 = 0

Goals accomplished! 🐙
a, b: MyNat
ih: a * b = b * a

succ
a * succ b = succ b * a
a, b: MyNat
ih: a * b = b * a

succ
a * succ b = succ b * a
a, b: MyNat
ih: a * b = b * a

succ
a * succ b = b * a + a
a, b: MyNat
ih: a * b = b * a

succ
a * succ b = b * a + a
a, b: MyNat
ih: a * b = b * a

succ
a * succ b = b * a + a
a, b: MyNat
ih: a * b = b * a

succ
a * succ b = a * b + a
a, b: MyNat
ih: a * b = b * a

succ
a * succ b = a * b + a
a, b: MyNat
ih: a * b = b * a

succ
a * succ b = a * b + a
a, b: MyNat
ih: a * b = b * a

succ
a * b + a = a * b + a

Goals 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)

lemma 
mul_left_comm: ∀ (a b c : MyNat), a * (b * c) = b * (a * c)
mul_left_comm
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) :
a: MyNat
a
* (
b: MyNat
b
*
c: MyNat
c
) =
b: MyNat
b
* (
a: MyNat
a
*
c: MyNat
c
) :=
a, b, c: MyNat

a * (b * c) = b * (a * c)
a, b, c: MyNat

a * (b * c) = b * (a * c)
a, b, c: MyNat

a * b * c = b * (a * c)
a, b, c: MyNat

a * b * c = b * (a * c)
a, b, c: MyNat

a * b * c = b * (a * c)
a, b, c: MyNat

b * a * c = b * (a * c)
a, b, c: MyNat

b * a * c = b * (a * c)
a, b, c: MyNat

b * a * c = b * (a * c)
a, b, c: MyNat

b * (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: ∀ (a b c d e : MyNat), a * b * c * d * e = c * (b * e * a) * d
example
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
d: MyNat
d
e: MyNat
e
:
MyNat: Type
MyNat
) : (((
a: MyNat
a
*
b: MyNat
b
)*
c: MyNat
c
)*
d: MyNat
d
)*
e: MyNat
e
=(
c: MyNat
c
*((
b: MyNat
b
*
e: MyNat
e
)*
a: MyNat
a
))*
d: MyNat
d
:=
a, b, c, d, e: MyNat

a * b * c * d * e = c * (b * e * a) * d

Goals 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.

lemma 
zero_pow_zero: 0 ^ 0 = 1
zero_pow_zero
: (
0: MyNat
0
:
MyNat: Type
MyNat
) ^ (
0: MyNat
0
:
MyNat: Type
MyNat
) =
1: MyNat
1
:=

0 ^ 0 = 1

0 ^ 0 = 1

1 = 1

Goals 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.

lemma 
zero_pow_succ: ∀ (m : MyNat), 0 ^ succ m = 0
zero_pow_succ
(
m: MyNat
m
:
MyNat: Type
MyNat
) : (
0: MyNat
0
:
MyNat: Type
MyNat
) ^ (
succ: MyNat → MyNat
succ
m: MyNat
m
) =
0: MyNat
0
:=
m: MyNat

0 ^ succ m = 0
m: MyNat

0 ^ succ m = 0
m: MyNat

0 ^ m * 0 = 0
m: MyNat

0 ^ m * 0 = 0
m: MyNat

0 ^ m * 0 = 0
m: MyNat

0 = 0

Goals 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.

lemma 
pow_one: ∀ (a : MyNat), a ^ 1 = a
pow_one
(
a: MyNat
a
:
MyNat: Type
MyNat
) :
a: MyNat
a
^ (
1: MyNat
1
:
MyNat: Type
MyNat
) =
a: MyNat
a
:=
a: MyNat

a ^ 1 = a
a: MyNat

a ^ 1 = a
a: MyNat

a ^ succ 0 = a
a: MyNat

a ^ succ 0 = a
a: MyNat

a ^ succ 0 = a
a: MyNat

a ^ 0 * a = a
a: MyNat

a ^ 0 * a = a
a: MyNat

a ^ 0 * a = a
a: MyNat

1 * a = a
a: MyNat

1 * a = a
a: MyNat

1 * a = a
a: MyNat

a = a

Goals 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.

lemma 
one_pow: ∀ (m : MyNat), 1 ^ m = 1
one_pow
(
m: MyNat
m
:
MyNat: Type
MyNat
) : (
1: MyNat
1
:
MyNat: Type
MyNat
) ^
m: MyNat
m
=
1: MyNat
1
:=
m: MyNat

1 ^ m = 1
m: MyNat

1 ^ m = 1

zero
1 ^ zero = 1

zero
1 ^ zero = 1

zero
1 ^ 0 = 1

zero
1 ^ 0 = 1

zero
1 ^ 0 = 1

zero
1 = 1

Goals accomplished! 🐙
m: MyNat
ih: 1 ^ m = 1

succ
1 ^ succ m = 1
m: MyNat
ih: 1 ^ m = 1

succ
1 ^ succ m = 1
m: MyNat
ih: 1 ^ m = 1

succ
1 ^ m * 1 = 1
m: MyNat
ih: 1 ^ m = 1

succ
1 ^ m * 1 = 1
m: MyNat
ih: 1 ^ m = 1

succ
1 ^ m * 1 = 1
m: MyNat
ih: 1 ^ m = 1

succ
1 * 1 = 1
m: MyNat
ih: 1 ^ m = 1

succ
1 * 1 = 1
m: MyNat
ih: 1 ^ m = 1

succ
1 * 1 = 1
m: MyNat
ih: 1 ^ m = 1

succ
1 = 1

Goals 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.

lemma 
pow_add: ∀ (a m n : MyNat), a ^ (m + n) = a ^ m * a ^ n
pow_add
(
a: MyNat
a
m: MyNat
m
n: MyNat
n
:
MyNat: Type
MyNat
) :
a: MyNat
a
^ (
m: MyNat
m
+
n: MyNat
n
) =
a: MyNat
a
^
m: MyNat
m
*
a: MyNat
a
^
n: MyNat
n
:=
a, m, n: MyNat

a ^ (m + n) = a ^ m * a ^ n
a, m, n: MyNat

a ^ (m + n) = a ^ m * a ^ n
a, m: MyNat

zero
a ^ (m + zero) = a ^ m * a ^ zero
a, m: MyNat

zero
a ^ (m + zero) = a ^ m * a ^ zero
a, m: MyNat

zero
a ^ (m + 0) = a ^ m * a ^ 0
a, m: MyNat

zero
a ^ (m + 0) = a ^ m * a ^ 0
a, m: MyNat

zero
a ^ (m + 0) = a ^ m * a ^ 0
a, m: MyNat

zero
a ^ m = a ^ m * a ^ 0
a, m: MyNat

zero
a ^ m = a ^ m * a ^ 0
a, m: MyNat

zero
a ^ m = a ^ m * a ^ 0
a, m: MyNat

zero
a ^ m = a ^ m * 1
a, m: MyNat

zero
a ^ m = a ^ m * 1
a, m: MyNat

zero
a ^ m = a ^ m * 1
a, m: MyNat

zero
a ^ m = a ^ m

Goals accomplished! 🐙
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ (m + succ n) = a ^ m * a ^ succ n
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ (m + succ n) = a ^ m * a ^ succ n
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ succ (m + n) = a ^ m * a ^ succ n
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ succ (m + n) = a ^ m * a ^ succ n
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ succ (m + n) = a ^ m * a ^ succ n
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ (m + n) * a = a ^ m * a ^ succ n
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ (m + n) * a = a ^ m * a ^ succ n
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ (m + n) * a = a ^ m * a ^ succ n
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ (m + n) * a = a ^ m * (a ^ n * a)
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ (m + n) * a = a ^ m * (a ^ n * a)
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ (m + n) * a = a ^ m * (a ^ n * a)
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ m * a ^ n * a = a ^ m * (a ^ n * a)
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ m * a ^ n * a = a ^ m * (a ^ n * a)
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ m * a ^ n * a = a ^ m * (a ^ n * a)
a, m, n: MyNat
ih: a ^ (m + n) = a ^ m * a ^ n

succ
a ^ 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.

lemma 
mul_pow: ∀ (a b n : MyNat), (a * b) ^ n = a ^ n * b ^ n
mul_pow
(
a: MyNat
a
b: MyNat
b
n: MyNat
n
:
MyNat: Type
MyNat
) : (
a: MyNat
a
*
b: MyNat
b
) ^
n: MyNat
n
=
a: MyNat
a
^
n: MyNat
n
*
b: MyNat
b
^
n: MyNat
n
:=
a, b, n: MyNat

(a * b) ^ n = a ^ n * b ^ n
a, b, n: MyNat

(a * b) ^ n = a ^ n * b ^ n
a, b: MyNat

zero
(a * b) ^ zero = a ^ zero * b ^ zero
a, b: MyNat

zero
(a * b) ^ zero = a ^ zero * b ^ zero
a, b: MyNat

zero
(a * b) ^ 0 = a ^ 0 * b ^ 0
a, b: MyNat

zero
(a * b) ^ 0 = a ^ 0 * b ^ 0
a, b: MyNat

zero
(a * b) ^ 0 = a ^ 0 * b ^ 0
a, b: MyNat

zero
1 = 1 * b ^ 0
a, b: MyNat

zero
1 = a ^ 0 * b ^ 0
a, b: MyNat

zero
1 = 1 * 1
a, b: MyNat

zero
1 = 1 * 1
a, b: MyNat

zero
1 = 1

Goals accomplished! 🐙
a, b, n: MyNat
ih: (a * b) ^ n = a ^ n * b ^ n

succ
(a * b) ^ succ n = a ^ succ n * b ^ succ n
a, b, n: MyNat
ih: (a * b) ^ n = a ^ n * b ^ n

succ
(a * b) ^ succ n = a ^ succ n * b ^ succ n
a, b, n: MyNat
ih: (a * b) ^ n = a ^ n * b ^ n

succ
(a * b) ^ n * (a * b) = a ^ succ n * b ^ succ n
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

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 ^ n * b ^ n * (a * b) = a ^ n * a * (b ^ n * b)
a, b, n: MyNat
ih: (a * b) ^ n = a ^ n * b ^ n

succ
a ^ 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}.

lemma 
pow_pow: ∀ (a m n : MyNat), (a ^ m) ^ n = a ^ (m * n)
pow_pow
(
a: MyNat
a
m: MyNat
m
n: MyNat
n
:
MyNat: Type
MyNat
) : (
a: MyNat
a
^
m: MyNat
m
) ^
n: MyNat
n
=
a: MyNat
a
^ (
m: MyNat
m
*
n: MyNat
n
) :=
a, 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 ^ 0
a, m: MyNat

zero
(a ^ m) ^ 0 = a ^ 0
a, m: MyNat

zero
(a ^ m) ^ 0 = a ^ 0
a, m: MyNat

zero
1 = a ^ 0

Goals 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)

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)

succ
a ^ (m * n) * a ^ m = a ^ (m * n + m)
a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)

succ
a ^ (m * n) * a ^ m = a ^ (m * n + m)
a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)

succ
a ^ (m * n) * a ^ m = a ^ (m * n + m)
a, m, n: MyNat
ih: (a ^ m) ^ n = a ^ (m * n)

succ
a ^ (m * n) * a ^ m = a ^ (m * n) * a ^ m

Goals 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.

def 
two: MyNat
two
:
MyNat: Type
MyNat
:=
2: MyNat
2
def
two_eq_succ_one: two = succ 1
two_eq_succ_one
:
two: MyNat
two
=
succ: MyNat → MyNat
succ
1: MyNat
1
:=

two = succ 1

Goals accomplished! 🐙
lemma
one_plus_one: 1 + 1 = 2
one_plus_one
: (
1: MyNat
1
:
MyNat: Type
MyNat
) + (
1: MyNat
1
:
MyNat: Type
MyNat
) = (
2: MyNat
2
:
MyNat: Type
MyNat
) :=

1 + 1 = 2

Goals accomplished! 🐙
-- and we already have one_eq_succ_zero.

Now we are ready to tackle the proof:

lemma 
add_squared: ∀ (a b : MyNat), (a + b) ^ two = a ^ two + b ^ two + 2 * a * b
add_squared
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) : (
a: MyNat
a
+
b: MyNat
b
) ^
two: MyNat
two
=
a: MyNat
a
^
two: MyNat
two
+
b: MyNat
b
^
two: MyNat
two
+
2: MyNat
2
*
a: MyNat
a
*
b: MyNat
b
:=
a, b: MyNat

(a + b) ^ two = a ^ two + b ^ two + 2 * a * b
a, b: MyNat

(a + b) ^ two = a ^ two + b ^ two + 2 * a * b
a, b: MyNat

(a + b) ^ succ 1 = a ^ succ 1 + b ^ succ 1 + 2 * a * b
a, b: MyNat

(a + b) ^ succ 1 = a ^ succ 1 + b ^ succ 1 + 2 * a * b
a, b: MyNat

(a + b) ^ succ 1 = a ^ succ 1 + b ^ succ 1 + 2 * a * b
a, b: MyNat

(a + b) ^ succ (succ 0) = a ^ succ (succ 0) + b ^ succ (succ 0) + 2 * a * b
a, b: MyNat

(a + b) ^ succ (succ 0) = a ^ succ (succ 0) + b ^ succ (succ 0) + 2 * a * b
a, b: MyNat

(a + b) ^ succ (succ 0) = a ^ succ (succ 0) + b ^ succ (succ 0) + 2 * a * b
a, b: MyNat

(a + b) ^ 0 * (a + b) * (a + b) = a ^ 0 * a * a + b ^ succ (succ 0) + 2 * a * b
a, b: MyNat

(a + b) ^ 0 * (a + b) * (a + b) = a ^ 0 * a * a + b ^ succ 0 * b + 2 * a * b
a, b: MyNat

(a + b) ^ 0 * (a + b) * (a + b) = a ^ succ 0 * a + b ^ succ (succ 0) + 2 * a * b
a, b: MyNat

(a + b) ^ 0 * (a + b) * (a + b) = a ^ 0 * a * a + b ^ 0 * b * b + 2 * a * b
a, b: MyNat

1 * (a + b) * (a + b) = a ^ 0 * a * a + b ^ 0 * b * b + 2 * a * b
a, b: MyNat

1 * (a + b) * (a + b) = 1 * a * a + 1 * b * b + 2 * a * b
a, b: MyNat

1 * (a + b) * (a + b) = 1 * a * a + b ^ 0 * b * b + 2 * a * b
a, b: MyNat

1 * (a + b) * (a + b) = 1 * a * a + 1 * b * b + 2 * a * b
a, b: MyNat

(a + b) * (a + b) = a * a + 1 * b * b + 2 * a * b
a, b: MyNat

(a + b) * (a + b) = a * a + 1 * b * b + 2 * a * b
a, b: MyNat

(a + b) * (a + b) = a * a + 1 * b * b + 2 * a * b
a, b: MyNat

(a + b) * (a + b) = a * a + b * b + 2 * a * b
a, b: MyNat

(a + b) * a + (a + b) * b = a * a + b * b + 2 * a * b
a, b: MyNat

(a + b) * a + (a + b) * b = a * a + b * b + 2 * a * b
a, b: MyNat

(a + b) * a + (a + b) * b = a * a + b * b + 2 * a * b
a, b: MyNat

a * a + b * a + (a * b + b * b) = a * a + b * b + 2 * a * b
a, b: MyNat

a * a + b * a + (a * b + b * b) = a * a + b * b + 2 * a * b
a, b: MyNat

a * a + b * a + (a + b) * b = a * a + b * b + 2 * a * b
a, b: MyNat

a * a + b * a + (a * b + b * b) = a * a + b * b + 2 * a * b
a, b: MyNat

a * a + b * a + (a * b + b * b) = a * a + b * b + (1 + 1) * a * b
a, b: MyNat

a * a + b * a + (a * b + b * b) = a * a + b * b + (1 + 1) * a * b
a, b: MyNat

a * a + b * a + (a * b + b * b) = a * a + b * b + (1 + 1) * a * b
a, b: MyNat

a * a + b * a + (a * b + b * b) = a * a + b * b + (1 + 1) * a * b
a, b: MyNat

a * a + b * a + (a * b + b * b) = a * a + b * b + (1 * a * b + 1 * a * b)
a, b: MyNat

a * a + b * a + (a * b + b * b) = a * a + b * b + (1 * a + 1 * a) * b
a, b: MyNat

a * a + b * a + (a * b + b * b) = a * a + b * b + (1 * a * b + 1 * a * b)
a, b: MyNat

a * a + b * a + (a * b + b * b) = a * a + b * b + (1 * a * b + 1 * a * b)
a, b: MyNat

a * a + b * a + (a * b + b * b) = a * a + b * b + (a * b + a * b)
a, b: MyNat

a * 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: (P Q : Type) → P → (P → Q) → Q
example
(
P: Type
P
Q: Type
Q
:
Type: Type 1
Type
) (
p: P
p
:
P: Type
P
) (
h: P → Q
h
:
P: Type
P
Q: Type
Q
) :
Q: Type
Q
:=
P, Q: Type
p: P
h: P Q

Q

Goals 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: MyNat → MyNat
example
:
MyNat: Type
MyNat
MyNat: Type
MyNat
:=

MyNat MyNat
n: MyNat

MyNat

Goals 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:

diagram

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: (P Q R S T U : Type) → P → (P → Q) → (Q → R) → (Q → T) → (S → T) → (T → U) → U
example
(
P: Type
P
Q: Type
Q
R: Type
R
S: Type
S
T: Type
T
U: Type
U
:
Type: Type 1
Type
) (
p: P
p
:
P: Type
P
) (
h: P → Q
h
:
P: Type
P
Q: Type
Q
) (
Warning: unused variable `i` [linter.unusedVariables]
:
Q: Type
Q
R: Type
R
) (
j: Q → T
j
:
Q: Type
Q
T: Type
T
) (
Warning: unused variable `k` [linter.unusedVariables]
:
S: Type
S
T: Type
T
) (
l: T → U
l
:
T: Type
T
U: Type
U
) :
U: Type
U
:=
P, Q, R, S, T, U: Type
p: P
h: P Q
i: Q R
j: Q T
k: S T
l: T U

U
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

U
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
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

Goals 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:

diagram

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 haves 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 applying 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: (P Q R S T U : Type) → P → (P → Q) → (Q → R) → (Q → T) → (S → T) → (T → U) → U
example
(
P: Type
P
Q: Type
Q
R: Type
R
S: Type
S
T: Type
T
U: Type
U
:
Type: Type 1
Type
) (
p: P
p
:
P: Type
P
) (
h: P → Q
h
:
P: Type
P
Q: Type
Q
) (
Warning: unused variable `i` [linter.unusedVariables]
:
Q: Type
Q
R: Type
R
) (
j: Q → T
j
:
Q: Type
Q
T: Type
T
) (
Warning: unused variable `k` [linter.unusedVariables]
:
S: Type
S
T: Type
T
) (
l: T → U
l
:
T: Type
T
U: Type
U
) :
U: Type
U
:=
P, Q, R, S, T, U: Type
p: P
h: P Q
i: Q R
j: Q T
k: S T
l: T U

U
P, Q, R, S, T, U: Type
p: P
h: P Q
i: Q R
j: Q T
k: S T
l: T U

T
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
P, Q, R, S, T, U: Type
p: P
h: P Q
i: Q R
j: Q T
k: S T
l: T U

P

Goals 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: (P Q : Type) → P → Q → P
example
(
P: Type
P
Q: Type
Q
:
Type: Type 1
Type
) :
P: Type
P
(
Q: Type
Q
P: Type
P
) :=
P, Q: Type

P Q P
P, Q: Type
p: P

Q P
P, Q: Type
p: P

Q P
Warning: unused variable `q` [linter.unusedVariables]
P, Q: Type
p: P
q: Q

P

Goals 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: (P Q R : Type) → (P → Q → R) → (P → Q) → P → R
example
(
P: Type
P
Q: Type
Q
R: Type
R
:
Type: Type 1
Type
) : (
P: Type
P
(
Q: Type
Q
R: Type
R
)) ((
P: Type
P
Q: Type
Q
) (
P: Type
P
R: Type
R
)) :=
P, Q, R: Type

(P Q R) (P Q) P R
P, Q, R: Type
f: P Q R

(P Q) P R
P, Q, R: Type
f: P Q R
h: P Q

P R
P, Q, R: Type
f: P Q R
h: P Q
p: P

R
P, Q, R: Type
f: P Q R
h: P Q
p: P
j: Q R

R
P, Q, R: Type
f: P Q R
h: P Q
p: P
j: Q R

Q
P, Q, R: Type
f: P Q R
h: P Q
p: P
j: Q R

P

Goals 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: (P Q F : Type) → (P → Q) → (Q → F) → P → F
example
(
P: Type
P
Q: Type
Q
F: Type
F
:
Type: Type 1
Type
) : (
P: Type
P
Q: Type
Q
) ((
Q: Type
Q
F: Type
F
) (
P: Type
P
F: Type
F
)) :=
P, Q, F: Type

(P Q) (Q F) P F
P, Q, F: Type
f: P Q
h: Q F
p: P

F
P, Q, F: Type
f: P Q
h: Q F
p: P

Q
P, Q, F: Type
f: P Q
h: Q F
p: P

P

Goals 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: {empty : Sort u_1} → (P Q : Type) → (P → Q) → (Q → empty) → P → empty
example
(
P: Type
P
Q: Type
Q
:
Type: Type 1
Type
) : (
P: Type
P
Q: Type
Q
) ((
Q: Type
Q
empty: Sort u_1
empty
) (
P: Type
P
empty: Sort u_1
empty
)) :=
empty: Sort ?u.27
P, Q: Type

(P Q) (Q empty) P empty
empty: Sort ?u.27
P, Q: Type
f: P Q
h: Q empty
p: P

empty
empty: Sort ?u.27
P, Q: Type
f: P Q
h: Q empty
p: P

Q
empty: Sort ?u.27
P, Q: Type
f: P Q
h: Q empty
p: P

P

Goals 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: (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 → L
example
(
A: Type
A
B: Type
B
C: Type
C
D: Type
D
E: Type
E
F: Type
F
G: Type
G
H: Type
H
I: Type
I
J: Type
J
K: Type
K
L: Type
L
:
Type: Type 1
Type
) (
f1: A → B
f1
:
A: Type
A
B: Type
B
) (
f2: B → E
f2
:
B: Type
B
E: Type
E
) (
Warning: unused variable `f3` [linter.unusedVariables]
:
E: Type
E
D: Type
D
) (
Warning: unused variable `f4` [linter.unusedVariables]
:
D: Type
D
A: Type
A
) (
f5: E → F
f5
:
E: Type
E
F: Type
F
) (
Warning: unused variable `f6` [linter.unusedVariables]
:
F: Type
F
C: Type
C
) (
Warning: unused variable `f7` [linter.unusedVariables]
:
B: Type
B
C: Type
C
) (
f8: F → G
f8
:
F: Type
F
G: Type
G
) (
f9: G → J
f9
:
G: Type
G
J: Type
J
) (
Warning: unused variable `f10` [linter.unusedVariables]
:
I: Type
I
J: Type
J
) (
f11: J → I
f11
:
J: Type
J
I: Type
I
) (
Warning: unused variable `f12` [linter.unusedVariables]
:
I: Type
I
H: Type
H
) (
Warning: unused variable `f13` [linter.unusedVariables]
:
E: Type
E
H: Type
H
) (
Warning: unused variable `f14` [linter.unusedVariables]
:
H: Type
H
K: Type
K
) (
f15: I → L
f15
:
I: Type
I
L: Type
L
) :
A: Type
A
L: Type
L
:=
A, 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 L
A, 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: A

L
A, 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: A

I
A, 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: A

J
A, 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: A

G
A, 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: A

F
A, 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: A

E
A, 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: A

B
A, 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: A

A

Goals 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: ∀ (P Q : Prop), P → (P → Q) → Q
example
(
P: Prop
P
Q: Prop
Q
:
Prop: Type
Prop
) (
p: P
p
:
P: Prop
P
) (
h: P → Q
h
:
P: Prop
P
Q: Prop
Q
) :
Q: Prop
Q
:=
P, Q: Prop
p: P
h: P Q

Q

Goals 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.

lemma 
imp_self: ∀ (P : Prop), P → P
imp_self
(
P: Prop
P
:
Prop: Type
Prop
) :
P: Prop
P
P: Prop
P
:=
P: Prop

P P
P: Prop
p: P

P

Goals 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:

diagram

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.

lemma 
maze: ∀ (P Q R S T U : Prop), P → (P → Q) → (Q → R) → (Q → T) → (S → T) → (T → U) → U
maze
(
P: Prop
P
Q: Prop
Q
R: Prop
R
S: Prop
S
T: Prop
T
U: Prop
U
:
Prop: Type
Prop
) (
p: P
p
:
P: Prop
P
) (
h: P → Q
h
:
P: Prop
P
Q: Prop
Q
) (
Warning: unused variable `i` [linter.unusedVariables]
:
Q: Prop
Q
R: Prop
R
) (
j: Q → T
j
:
Q: Prop
Q
T: Prop
T
) (
Warning: unused variable `k` [linter.unusedVariables]
:
S: Prop
S
T: Prop
T
) (
l: T → U
l
:
T: Prop
T
U: Prop
U
) :
U: Prop
U
:=
P, Q, R, S, T, U: Prop
p: P
h: P Q
i: Q R
j: Q T
k: S T
l: T U

U
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

U
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
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

Goals 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:

diagram

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 haves 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 applying 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.

lemma 
maze₂: ∀ (P Q R S T U : Prop), P → (P → Q) → (Q → R) → (Q → T) → (S → T) → (T → U) → U
maze₂
(
P: Prop
P
Q: Prop
Q
R: Prop
R
S: Prop
S
T: Prop
T
U: Prop
U
:
Prop: Type
Prop
) (
p: P
p
:
P: Prop
P
) (
h: P → Q
h
:
P: Prop
P
Q: Prop
Q
) (
Warning: unused variable `i` [linter.unusedVariables]
:
Q: Prop
Q
R: Prop
R
) (
j: Q → T
j
:
Q: Prop
Q
T: Prop
T
) (
Warning: unused variable `k` [linter.unusedVariables]
:
S: Prop
S
T: Prop
T
) (
l: T → U
l
:
T: Prop
T
U: Prop
U
) :
U: Prop
U
:=
P, Q, R, S, T, U: Prop
p: P
h: P Q
i: Q R
j: Q T
k: S T
l: T U

U
P, Q, R, S, T, U: Prop
p: P
h: P Q
i: Q R
j: Q T
k: S T
l: T U

T
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
P, Q, R, S, T, U: Prop
p: P
h: P Q
i: Q R
j: Q T
k: S T
l: T U

P

Goals 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: ∀ (P Q : Prop), P → Q → P
example
(
P: Prop
P
Q: Prop
Q
:
Prop: Type
Prop
) :
P: Prop
P
(
Q: Prop
Q
P: Prop
P
) :=
P, Q: Prop

P Q P
P, Q: Prop
p: P

Q P
P, Q: Prop
p: P

Q P
Warning: unused variable `q` [linter.unusedVariables]
P, Q: Prop
p: P
q: Q

P

Goals 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: ∀ (P Q R : Prop), (P → Q → R) → (P → Q) → P → R
example
(
P: Prop
P
Q: Prop
Q
R: Prop
R
:
Prop: Type
Prop
) : (
P: Prop
P
(
Q: Prop
Q
R: Prop
R
)) ((
P: Prop
P
Q: Prop
Q
) (
P: Prop
P
R: Prop
R
)) :=
P, Q, R: Prop

(P Q R) (P Q) P R
P, Q, R: Prop
f: P Q R

(P Q) P R
P, Q, R: Prop
f: P Q R
h: P Q

P R
P, Q, R: Prop
f: P Q R
h: P Q
p: P

R
P, Q, R: Prop
f: P Q R
h: P Q
p: P
j: Q R

R
P, Q, R: Prop
f: P Q R
h: P Q
p: P
j: Q R

Q
P, Q, R: Prop
f: P Q R
h: P Q
p: P
j: Q R

P

Goals 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.

lemma 
imp_trans: ∀ (P Q R : Prop), (P → Q) → (Q → R) → P → R
imp_trans
(
P: Prop
P
Q: Prop
Q
R: Prop
R
:
Prop: Type
Prop
) : (
P: Prop
P
Q: Prop
Q
) ((
Q: Prop
Q
R: Prop
R
) (
P: Prop
P
R: Prop
R
)) :=
P, Q, R: Prop

(P Q) (Q R) P R
P, Q, R: Prop
hpq: P Q
hqr: Q R

P R
P, Q, R: Prop
hpq: P Q
hqr: Q R
p: P

R
P, Q, R: Prop
hpq: P Q
hqr: Q R
p: P

Q
P, Q, R: Prop
hpq: P Q
hqr: Q R
p: P

P

Goals 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:

theorem 
not_iff_imp_false: ∀ (P : Prop), ¬P ↔ P → false = true
not_iff_imp_false
(
P: Prop
P
:
Prop: Type
Prop
) : ¬
P: Prop
P
(
P: Prop
P
false: Bool
false
) :=
P: Prop

¬P P false = true

Goals 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.

lemma 
contrapositive: ∀ (P Q : Prop), (P → Q) → ¬Q → ¬P
contrapositive
(
P: Prop
P
Q: Prop
Q
:
Prop: Type
Prop
) : (
P: Prop
P
Q: Prop
Q
) (¬
Q: Prop
Q
¬
P: Prop
P
) :=
P, Q: Prop

(P Q) ¬Q ¬P
P, Q: Prop

(P Q) ¬Q ¬P
P, Q: Prop

(P Q) ¬Q ¬P
P, Q: Prop

(P Q) (Q false = true) P false = true
P, Q: Prop

(P Q) (Q false = true) ¬P
P, Q: Prop
f: P Q

(Q false = true) P false = true
P, Q: Prop
f: P Q
h: Q false = true

P false = true
P, Q: Prop
f: P Q
h: Q false = true
p: P

false = true
P, Q: Prop
f: P Q
h: Q false = true
p: P

Q
P, Q: Prop
f: P Q
h: Q false = true
p: P

P

Goals 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: ∀ (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 → L
example
(
A: Prop
A
B: Prop
B
C: Prop
C
D: Prop
D
E: Prop
E
F: Prop
F
G: Prop
G
H: Prop
H
I: Prop
I
J: Prop
J
K: Prop
K
L: Prop
L
:
Prop: Type
Prop
) (
f1: A → B
f1
:
A: Prop
A
B: Prop
B
) (
f2: B → E
f2
:
B: Prop
B
E: Prop
E
) (
Warning: unused variable `f3` [linter.unusedVariables]
:
E: Prop
E
D: Prop
D
) (
Warning: unused variable `f4` [linter.unusedVariables]
:
D: Prop
D
A: Prop
A
) (
f5: E → F
f5
:
E: Prop
E
F: Prop
F
) (
Warning: unused variable `f6` [linter.unusedVariables]
:
F: Prop
F
C: Prop
C
) (
Warning: unused variable `f7` [linter.unusedVariables]
:
B: Prop
B
C: Prop
C
) (
f8: F → G
f8
:
F: Prop
F
G: Prop
G
) (
f9: G → J
f9
:
G: Prop
G
J: Prop
J
) (
Warning: unused variable `f10` [linter.unusedVariables]
:
I: Prop
I
J: Prop
J
) (
f11: J → I
f11
:
J: Prop
J
I: Prop
I
) (
Warning: unused variable `f12` [linter.unusedVariables]
:
I: Prop
I
H: Prop
H
) (
Warning: unused variable `f13` [linter.unusedVariables]
:
E: Prop
E
H: Prop
H
) (
Warning: unused variable `f14` [linter.unusedVariables]
:
H: Prop
H
K: Prop
K
) (
f15: I → L
f15
:
I: Prop
I
L: Prop
L
) :
A: Prop
A
L: Prop
L
:=
A, 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 L
A, 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: A

L
A, 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: A

I
A, 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: A

J
A, 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: A

G
A, 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: A

F
A, 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: A

E
A, 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: A

B
A, 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: A

A

Goals accomplished! 🐙
-- BUGBUG: NNG uses cc which is missing in lean4...

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: ∀ (P Q : Prop), P → Q → P ∧ Q
example
(
P: Prop
P
Q: Prop
Q
:
Prop: Type
Prop
) (
p: P
p
:
P: Prop
P
) (
q: Q
q
:
Q: Prop
Q
) :
P: Prop
P
Q: Prop
Q
:=
P, Q: Prop
p: P
q: Q

P Q
P, Q: Prop
p: P
q: Q

left
P
P, Q: Prop
p: P
q: Q
Q
P, Q: Prop
p: P
q: Q

right
Q

Goals 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.

lemma 
and_symm: ∀ (P Q : Prop), P ∧ Q → Q ∧ P
and_symm
(
P: Prop
P
Q: Prop
Q
:
Prop: Type
Prop
) :
P: Prop
P
Q: Prop
Q
Q: Prop
Q
P: Prop
P
:=
P, Q: Prop

P Q Q P
P, Q: Prop
h: P Q

Q P
P, Q: Prop
h: P Q

Q P
P, Q: Prop
p: P
q: Q

intro
Q P
P, Q: Prop
p: P
q: Q

intro.left
Q
P, Q: Prop
p: P
q: Q
P
P, Q: Prop
p: P
q: Q

intro.right
P

Goals 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.

lemma 
and_trans: ∀ (P Q R : Prop), P ∧ Q → Q ∧ R → P ∧ R
and_trans
(
P: Prop
P
Q: Prop
Q
R: Prop
R
:
Prop: Type
Prop
) :
P: Prop
P
Q: Prop
Q
Q: Prop
Q
R: Prop
R
P: Prop
P
R: Prop
R
:=
P, 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 R

P R
P, Q, R: Prop
hpq: P Q
hqr: Q R

P R
P, Q, R: Prop
hqr: Q R
p: P
q: Q

intro
P R
P, Q, R: Prop
hqr: Q R
p: P
q: Q

intro
P R
P, Q, R: Prop
p: P
q, q': Q
r: R

intro.intro
P R
P, Q, R: Prop
p: P
q, q': Q
r: R

intro.intro.left
P
P, Q, R: Prop
p: P
q, q': Q
r: R
R
P, Q, R: Prop
p: P
q, q': Q
r: R

intro.intro.right
R

Goals 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.

lemma 
iff_trans: ∀ (P Q R : Prop), (P ↔ Q) → (Q ↔ R) → (P ↔ R)
iff_trans
(
P: Prop
P
Q: Prop
Q
R: Prop
R
:
Prop: Type
Prop
) : (
P: Prop
P
Q: Prop
Q
) (
Q: Prop
Q
R: Prop
R
) (
P: Prop
P
R: Prop
R
) :=
P, 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 R

P R
P, Q, R: Prop
hpq: P Q
hqr: Q R

mp
P R
P, Q, R: Prop
hpq: P Q
hqr: Q R
R P
P, Q, R: Prop
hpq: P Q
hqr: Q R

mp
P R
P, Q, R: Prop
hqr: Q R
pq: P Q
qp: Q P

mp.intro
P R
P, Q, R: Prop
hqr: Q R
pq: P Q
qp: Q P

mp.intro
P R
P, Q, R: Prop
pq: P Q
qp: Q P
qr: Q R
rq: R Q

mp.intro.intro
P R
P, Q, R: Prop
pq: P Q
qp: Q P
qr: Q R
rq: R Q
p: P

mp.intro.intro
R
P, Q, R: Prop
pq: P Q
qp: Q P
qr: Q R
rq: R Q
p: P

mp.intro.intro
Q
P, Q, R: Prop
pq: P Q
qp: Q P
qr: Q R
rq: R Q
p: P

mp.intro.intro
P

Goals accomplished! 🐙
P, Q, R: Prop
hpq: P Q
hqr: Q R

mpr
R P
P, Q, R: Prop
hqr: Q R
pq: P Q
qp: Q P

mpr.intro
R P
P, Q, R: Prop
hqr: Q R
pq: P Q
qp: Q P

mpr.intro
R P
P, Q, R: Prop
pq: P Q
qp: Q P
qr: Q R
rq: R Q

mpr.intro.intro
R P
P, Q, R: Prop
pq: P Q
qp: Q P
qr: Q R
rq: R Q
r: R

mpr.intro.intro
P
P, Q, R: Prop
pq: P Q
qp: Q P
qr: Q R
rq: R Q
r: R

mpr.intro.intro
Q
P, Q, R: Prop
pq: P Q
qp: Q P
qr: Q R
rq: R Q
r: R

mpr.intro.intro
R

Goals 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.

lemma 
iff_trans₂: ∀ (P Q R : Prop), (P ↔ Q) → (Q ↔ R) → (P ↔ R)
iff_trans₂
(
P: Prop
P
Q: Prop
Q
R: Prop
R
:
Prop: Type
Prop
) : (
P: Prop
P
Q: Prop
Q
) (
Q: Prop
Q
R: Prop
R
) (
P: Prop
P
R: Prop
R
) :=
P, Q, R: Prop

(P Q) (Q R) (P R)
P, Q, R: Prop
hpq: P Q
hqr: Q R

P R
P, Q, R: Prop
hpq: P Q
hqr: Q R

mp
P R
P, Q, R: Prop
hpq: P Q
hqr: Q R
R P
P, Q, R: Prop
hpq: P Q
hqr: Q R
p: P

mp
R
P, Q, R: Prop
hpq: P Q
hqr: Q R
R P
P, Q, R: Prop
hpq: P Q
hqr: Q R
p: P

mp
Q
P, Q, R: Prop
hpq: P Q
hqr: Q R
R P
P, Q, R: Prop
hpq: P Q
hqr: Q R
p: P

mp
P
P, Q, R: Prop
hpq: P Q
hqr: Q R
R P
P, Q, R: Prop
hpq: P Q
hqr: Q R

mpr
R P
P, Q, R: Prop
hpq: P Q
hqr: Q R
r: R

mpr
P
P, Q, R: Prop
hpq: P Q
hqr: Q R
r: R

mpr
Q
P, Q, R: Prop
hpq: P Q
hqr: Q R
r: R

mpr
R

Goals accomplished! 🐙

Another trick

Instead of using cases on h : P ↔ Q, you can just rw [h], and this will change all Ps to Qs 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.

lemma 
iff_trans₃: ∀ (P Q R : Prop), (P ↔ Q) → (Q ↔ R) → (P ↔ R)
iff_trans₃
(
P: Prop
P
Q: Prop
Q
R: Prop
R
:
Prop: Type
Prop
) : (
P: Prop
P
Q: Prop
Q
) (
Q: Prop
Q
R: Prop
R
) (
P: Prop
P
R: Prop
R
) :=
P, Q, R: Prop

(P Q) (Q R) (P R)
P, Q, R: Prop
hpq: P Q
hqr: Q R

P R
P, Q, R: Prop
hpq: P Q
hqr: Q R

P R
P, Q, R: Prop
hpq: P Q
hqr: Q R

Q R
P, Q, R: Prop
hpq: P Q
hqr: Q R

Q R
P, Q, R: Prop
hpq: P Q
hqr: Q R

Q R
P, Q, R: Prop
hpq: P Q
hqr: Q R

R R

Goals 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: ∀ (P Q : Prop), Q → P ∨ Q
example
(
P: Prop
P
Q: Prop
Q
:
Prop: Type
Prop
) :
Q: Prop
Q
(
P: Prop
P
Q: Prop
Q
) :=
P, Q: Prop

Q P Q
P, Q: Prop
q: Q

P Q
P, Q: Prop
q: Q

h
Q

Goals 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.

lemma 
or_symm: ∀ (P Q : Prop), P ∨ Q → Q ∨ P
or_symm
(
P: Prop
P
Q: Prop
Q
:
Prop: Type
Prop
) :
P: Prop
P
Q: Prop
Q
Q: Prop
Q
P: Prop
P
:=
P, Q: Prop

P Q Q P
P, Q: Prop
h: P Q

Q P
P, Q: Prop
h: P Q

Q P
P, Q: Prop
hp: P

inl
Q P
P, Q: Prop
hp: P

inl.h
P

Goals accomplished! 🐙
P, Q: Prop
hq: Q

inr
Q P
P, Q: Prop
hq: Q

inr.h
Q

Goals 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).

lemma 
and_or_distrib_left: ∀ (P Q R : Prop), P ∧ (Q ∨ R) ↔ P ∧ Q ∨ P ∧ R
and_or_distrib_left
(
P: Prop
P
Q: Prop
Q
R: Prop
R
:
Prop: Type
Prop
) :
P: Prop
P
(
Q: Prop
Q
R: Prop
R
) (
P: Prop
P
Q: Prop
Q
) (
P: Prop
P
R: Prop
R
) :=
P, Q, R: Prop

P (Q R) P Q P R
P, Q, R: Prop

mp
P (Q R) P Q P R
P, Q, R: Prop
P Q P R P (Q R)
P, Q, R: Prop

mp
P (Q R) P Q P R
P, Q, R: Prop
HP: P
HQ: Q

mp.intro.inl
P Q P R
P, Q, R: Prop
HP: P
HR: R
P Q P R
P, Q, R: Prop
HP: P
HQ: Q

mp.intro.inl.h
P Q
P, Q, R: Prop
HP: P
HR: R
P Q P R
;
P, Q, R: Prop
HP: P
HQ: Q

mp.intro.inl.h.left
P
P, Q, R: Prop
HP: P
HQ: Q
Q
P, Q, R: Prop
HP: P
HQ: Q

mp.intro.inl.h.left
P
P, Q, R: Prop
HP: P
HQ: Q
Q

Goals accomplished! 🐙
;
P, Q, R: Prop
HP: P
HR: R

mp.intro.inr.h
P R
;
P, Q, R: Prop
HP: P
HR: R

mp.intro.inr.h.left
P
P, Q, R: Prop
HP: P
HR: R
R
P, Q, R: Prop
HP: P
HR: R

mp.intro.inr.h.left
P
P, Q, R: Prop
HP: P
HR: R
R

Goals accomplished! 🐙
P, Q, R: Prop

mpr
P Q P R P (Q R)
P, Q, R: Prop

mpr
P Q P R P (Q R)
P, Q, R: Prop
HP: P
HQ: Q

mpr.inl.intro
P (Q R)
P, Q, R: Prop
HP: P
HR: R
P (Q R)
P, Q, R: Prop
HP: P
HQ: Q

mpr.inl.intro.left
P
P, Q, R: Prop
HP: P
HQ: Q
Q R
P, Q, R: Prop
HP: P
HR: R
P (Q R)
;
P, Q, R: Prop
HP: P
HQ: Q

mpr.inl.intro.right
Q R
P, Q, R: Prop
HP: P
HR: R
P (Q R)
;
P, Q, R: Prop
HP: P
HQ: Q

mpr.inl.intro.right.h
Q
P, Q, R: Prop
HP: P
HR: R
P (Q R)
;
P, Q, R: Prop
HP: P
HR: R

mpr.inr.intro
P (Q R)
P, Q, R: Prop
HP: P
HR: R

mpr.inr.intro.left
P
P, Q, R: Prop
HP: P
HR: R
Q R
;
P, Q, R: Prop
HP: P
HR: R

mpr.inr.intro.right
Q R
;
P, Q, R: Prop
HP: P
HR: R

mpr.inr.intro.right.h
R
;

Goals 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:

lemma 
and_or_distrib_left₂: ∀ (P Q R : Prop), P ∧ (Q ∨ R) ↔ P ∧ Q ∨ P ∧ R
and_or_distrib_left₂
(
P: Prop
P
Q: Prop
Q
R: Prop
R
:
Prop: Type
Prop
) :
P: Prop
P
(
Q: Prop
Q
R: Prop
R
) (
P: Prop
P
Q: Prop
Q
) (
P: Prop
P
R: Prop
R
) :=
P, Q, R: Prop

P (Q R) P Q P R
P, Q, R: Prop

mp
P (Q R) P Q P R
P, Q, R: Prop
P Q P R P (Q R)
P, Q, R: Prop

mp
P (Q R) P Q P R
P, Q, R: Prop
HP: P
HQ: Q

mp.intro.inl
P Q P R
P, Q, R: Prop
HP: P
HR: R
P Q P R
P, Q, R: Prop
HP: P
HQ: Q

mp.intro.inl.h
P Q
P, Q, R: Prop
HP: P
HR: R
P Q P R
;
P, Q, R: Prop
HP: P
HQ: Q

mp.intro.inl.h.left
P
P, Q, R: Prop
HP: P
HQ: Q
Q
P, Q, R: Prop
HP: P
HQ: Q

mp.intro.inl.h.left
P
P, Q, R: Prop
HP: P
HQ: Q
Q

Goals accomplished! 🐙
P, Q, R: Prop
HP: P
HR: R

mp.intro.inr.h
P R
;
P, Q, R: Prop
HP: P
HR: R

mp.intro.inr.h.left
P
P, Q, R: Prop
HP: P
HR: R
R
P, Q, R: Prop
HP: P
HR: R

mp.intro.inr.h.left
P
P, Q, R: Prop
HP: P
HR: R
R

Goals accomplished! 🐙
P, Q, R: Prop

mpr
P Q P R P (Q R)
P, Q, R: Prop
HP: P
HQ: Q

mpr.inl.intro
P (Q R)
P, Q, R: Prop
HP: P
HR: R
P (Q R)
P, Q, R: Prop
HP: P
HQ: Q

mpr.inl.intro.left
P
P, Q, R: Prop
HP: P
HQ: Q
Q R
P, Q, R: Prop
HP: P
HR: R
P (Q R)
;
P, Q, R: Prop
HP: P
HQ: Q

mpr.inl.intro.right
Q R
P, Q, R: Prop
HP: P
HR: R
P (Q R)
;
P, Q, R: Prop
HP: P
HQ: Q

mpr.inl.intro.right.h
Q
P, Q, R: Prop
HP: P
HR: R
P (Q R)
;
P, Q, R: Prop
HP: P
HR: R

mpr.inr.intro
P (Q R)
P, Q, R: Prop
HP: P
HR: R

mpr.inr.intro.left
P
P, Q, R: Prop
HP: P
HR: R
Q R
;
P, Q, R: Prop
HP: P
HR: R

mpr.inr.intro.right
Q R
;
P, Q, R: Prop
HP: P
HR: R

mpr.inr.intro.right.h
R
;

Goals 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.

lemma 
contra: ∀ (P Q : Prop), P ∧ ¬P → Q
contra
(
P: Prop
P
Q: Prop
Q
:
Prop: Type
Prop
) : (
P: Prop
P
¬
P: Prop
P
)
Q: Prop
Q
:=
P, Q: Prop

P ¬P Q
P, Q: Prop
h: P ¬P

Q
P, Q: Prop
h: P ¬P

Q
P, Q: Prop
p: P
np: ¬P

intro
Q
P, Q: Prop
p: P
np: ¬P

intro.h
False
P, Q: Prop
p: P
np: ¬P

intro.h
P

Goals 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).

lemma 
contrapositive2: ∀ (P Q : Prop), (¬Q → ¬P) → P → Q
contrapositive2
(
P: Prop
P
Q: Prop
Q
:
Prop: Type
Prop
) : (¬
Q: Prop
Q
¬
P: Prop
P
) (
P: Prop
P
Q: Prop
Q
) :=
P, Q: Prop

(¬Q ¬P) P Q
P, Q: Prop
h: ¬Q ¬P

P Q
P, Q: Prop
h: ¬Q ¬P
p: P

Q
P, Q: Prop
h: ¬Q ¬P
p: P
q: Q

pos
Q
P, Q: Prop
h: ¬Q ¬P
p: P
q: ¬Q
Q
P, Q: Prop
h: ¬Q ¬P
p: P
q: ¬Q

neg
Q
P, Q: Prop
h: ¬Q ¬P
p: P
q: ¬Q
np: ¬P

neg
Q

Goals 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.

theorem 
succ_inj': ∀ {a b : MyNat}, succ a = succ b → a = b
succ_inj'
{
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
} (
hs: succ a = succ b
hs
:
succ: MyNat → MyNat
succ
a: MyNat
a
=
succ: MyNat → MyNat
succ
b: MyNat
b
) :
a: MyNat
a
=
b: MyNat
b
:=
a, b: MyNat
hs: succ a = succ b

a = b

Goals accomplished! 🐙

Important thing.

You can rewrite proofs of equalities. If h : A = B then rw [h] changes As to Bs. 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.

theorem 
succ_succ_inj: ∀ {a b : MyNat}, succ (succ a) = succ (succ b) → a = b
succ_succ_inj
{
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
} (
h: succ (succ a) = succ (succ b)
h
:
succ: MyNat → MyNat
succ
(
succ: MyNat → MyNat
succ
a: MyNat
a
) =
succ: MyNat → MyNat
succ
(
succ: MyNat → MyNat
succ
b: MyNat
b
)) :
a: MyNat
a
=
b: MyNat
b
:=
a, b: MyNat
h: succ (succ a) = succ (succ b)

a = b
a, b: MyNat
h: succ (succ a) = succ (succ b)

a
succ a = succ b
a, b: MyNat
h: succ (succ a) = succ (succ b)

a.a
succ (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: ∀ {a b : MyNat}, succ (succ a) = succ (succ b) → a = b
example
{
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
} (
h: succ (succ a) = succ (succ b)
h
:
succ: MyNat → MyNat
succ
(
succ: MyNat → MyNat
succ
a: MyNat
a
) =
succ: MyNat → MyNat
succ
(
succ: MyNat → MyNat
succ
b: MyNat
b
)) :
a: MyNat
a
=
b: MyNat
b
:=
a, b: MyNat
h: succ (succ a) = succ (succ b)

a = b
a, b: MyNat
h: succ (succ a) = succ (succ b)

a
succ a = succ b

Goals accomplished! 🐙
example: ∀ {a b : MyNat}, succ (succ a) = succ (succ b) → a = b
example
{
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
} (
h: succ (succ a) = succ (succ b)
h
:
succ: MyNat → MyNat
succ
(
succ: MyNat → MyNat
succ
a: MyNat
a
) =
succ: MyNat → MyNat
succ
(
succ: MyNat → MyNat
succ
b: MyNat
b
)) :
a: MyNat
a
=
b: MyNat
b
:=
a, b: MyNat
h: succ (succ a) = succ (succ b)

a = b

Goals 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.

theorem 
succ_eq_succ_of_eq: ∀ {a b : MyNat}, a = b → succ a = succ b
succ_eq_succ_of_eq
{
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
} :
a: MyNat
a
=
b: MyNat
b
succ: MyNat → MyNat
succ
a: MyNat
a
=
succ: MyNat → MyNat
succ
b: MyNat
b
:=
a, b: MyNat

a = b succ a = succ b
a, b: MyNat
h: a = b

succ a = succ b
a, b: MyNat
h: a = b

succ a = succ b
a, b: MyNat
h: a = b

succ b = succ b

Goals 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.

theorem 
succ_eq_succ_iff: ∀ (a b : MyNat), succ a = succ b ↔ a = b
succ_eq_succ_iff
(
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
:=
a, b: MyNat

succ a = succ b a = b
a, b: MyNat

mp
succ a = succ b a = b
a, b: MyNat
a = b succ a = succ b
a, b: MyNat

mpr
a = b succ a = succ b

Goals 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.

theorem 
add_right_cancel: ∀ (a t b : MyNat), a + t = b + t → a = b
add_right_cancel
(
a: MyNat
a
t: MyNat
t
b: MyNat
b
:
MyNat: Type
MyNat
) :
a: MyNat
a
+
t: MyNat
t
=
b: MyNat
b
+
t: MyNat
t
a: MyNat
a
=
b: MyNat
b
:=
a, t, b: MyNat

a + t = b + t a = b
a, t, b: MyNat
h: a + t = b + t

a = b
a, t, b: MyNat
h: a + t = b + t

a = b
a, b: MyNat
h: a + zero = b + zero

zero
a = b
a, b: MyNat
h: a + zero = b + zero

zero
a = b
a, b: MyNat
h: a + 0 = b + 0

zero
a = b
a, b: MyNat
h: a + 0 = b + 0

zero
a = b
a, b: MyNat
h: a + 0 = b + 0

zero
a = b
a, b: MyNat
h: a + 0 = b + 0

zero
a = b
a, b: MyNat
h: a = b + 0

zero
a = b
a, b: MyNat
h: a = b + 0

zero
a = b
a, b: MyNat
h: a = b + 0

zero
a = b
a, b: MyNat
h: a = b + 0

zero
a = b
a, b: MyNat
h: a = b

zero
a = b
a, b: MyNat
h: a = b

zero
a = b
a, b: MyNat
h: a = b

zero
a = b

Goals accomplished! 🐙
a, b, d: MyNat
ih: a + d = b + d a = b
h: a + succ d = b + succ d

succ
a = b
a, b, d: MyNat
ih: a + d = b + d a = b
h: a + succ d = b + succ d

succ
a + d = b + d
a, b, d: MyNat
ih: a + d = b + d a = b
h: a + succ d = b + succ d

succ
a + d = b + d
a, b, d: MyNat
ih: a + d = b + d a = b
h: succ (a + d) = b + succ d

succ
a + d = b + d
a, b, d: MyNat
ih: a + d = b + d a = b
h: succ (a + d) = b + succ d

succ
a + d = b + d
a, b, d: MyNat
ih: a + d = b + d a = b
h: succ (a + d) = b + succ d

succ
a + d = b + d
a, b, d: MyNat
ih: a + d = b + d a = b
h: succ (a + d) = b + succ d

succ
a + d = b + d
a, b, d: MyNat
ih: a + d = b + d a = b
h: succ (a + d) = succ (b + d)

succ
a + d = b + d
a, b, d: MyNat
ih: a + d = b + d a = b
h: succ (a + d) = succ (b + d)

succ
a + d = b + d
a, b, d: MyNat
ih: a + d = b + d a = b
h: succ (a + d) = succ (b + d)

succ
a + d = b + d

Goals 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.

theorem 
add_left_cancel: ∀ (t a b : MyNat), t + a = t + b → a = b
add_left_cancel
(
t: MyNat
t
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
t: MyNat
t
+
a: MyNat
a
=
t: MyNat
t
+
b: MyNat
b
a: MyNat
a
=
b: MyNat
b
:=
t, a, b: MyNat

t + a = t + b a = b
t, a, b: MyNat

t + a = t + b a = b
t, a, b: MyNat

a + t = t + b a = b
t, a, b: MyNat

a + t = t + b a = b
t, a, b: MyNat

a + t = t + b a = b
t, a, b: MyNat

a + t = b + t a = b
t, a, b: MyNat

a + t = b + t a = b

Goals 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.

theorem 
add_right_cancel_iff: ∀ (t a b : MyNat), a + t = b + t ↔ a = b
add_right_cancel_iff
(
t: MyNat
t
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
a: MyNat
a
+
t: MyNat
t
=
b: MyNat
b
+
t: MyNat
t
a: MyNat
a
=
b: MyNat
b
:=
t, a, b: MyNat

a + t = b + t a = b
t, a, b: MyNat

mp
a + t = b + t a = b
t, a, b: MyNat
a = b a + t = b + t
t, a, b: MyNat

mpr
a = b a + t = b + t
t, a, b: MyNat
h: a = b

mpr
a + t = b + t
t, a, b: MyNat
h: a = b

mpr
a + t = b + t
t, a, b: MyNat
h: a = b

mpr
b + t = b + t

Goals 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.

lemma 
eq_zero_of_add_right_eq_self: ∀ {a b : MyNat}, a + b = a → b = 0
eq_zero_of_add_right_eq_self
{
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
} :
a: MyNat
a
+
b: MyNat
b
=
a: MyNat
a
b: MyNat
b
=
0: MyNat
0
:=
a, b: MyNat

a + b = a b = 0
a, b: MyNat
h: a + b = a

b = 0
a, b: MyNat
h: a + b = a

a
a + b = a + 0
a, b: MyNat
h: a + b = a

a
a + b = a + 0
a, b: MyNat
h: a + b = a

a
a = a + 0
a, b: MyNat
h: a + b = a

a
a = a + 0
a, b: MyNat
h: a + b = a

a
a = a + 0
a, b: MyNat
h: a + b = a

a
a = a

Goals 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.

theorem 
succ_ne_zero: ∀ (a : MyNat), succ a ≠ 0
succ_ne_zero
(
a: MyNat
a
:
MyNat: Type
MyNat
) :
succ: MyNat → MyNat
succ
a: MyNat
a
0: MyNat
0
:=
a: MyNat

succ a 0
a: MyNat

0 succ a

Goals 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, where b : 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.

lemma 
add_left_eq_zero: ∀ ⦃a b : MyNat⦄, a + b = 0 → b = 0
add_left_eq_zero
{{
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
}} (
h: a + b = 0
h
:
a: MyNat
a
+
b: MyNat
b
=
0: MyNat
0
) :
b: MyNat
b
=
0: MyNat
0
:=
a, b: MyNat
h: a + b = 0

b = 0
a, b: MyNat
h: a + b = 0

b = 0
a: MyNat
h: a + zero = 0

zero
zero = 0

Goals accomplished! 🐙
a, d: MyNat
h: a + succ d = 0

succ
succ d = 0
a, d: MyNat
h: a + succ d = 0

succ
succ d = 0
a, d: MyNat
h: succ (a + d) = 0

succ
succ d = 0
a, d: MyNat
h: succ (a + d) = 0

succ
succ d = 0
a, d: MyNat
h: succ (a + d) = 0

succ
succ d = 0
a, d: MyNat
h: succ (a + d) = 0

succ.h
False
a, d: MyNat
h: succ (a + d) = 0

succ.h
succ (a + d) = 0

Goals 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.

lemma 
add_right_eq_zero: ∀ {a b : MyNat}, a + b = 0 → a = 0
add_right_eq_zero
{
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
} :
a: MyNat
a
+
b: MyNat
b
=
0: MyNat
0
a: MyNat
a
=
0: MyNat
0
:=
a, b: MyNat

a + b = 0 a = 0
a, b: MyNat
h: a + b = 0

a = 0
a, b: MyNat
h: a + b = 0

a = 0
a, b: MyNat
h: b + a = 0

a = 0
a, b: MyNat
h: b + a = 0

a = 0
a, b: MyNat
h: b + a = 0

a = 0

Goals 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.

theorem 
add_one_eq_succ: ∀ (d : MyNat), d + 1 = succ d
add_one_eq_succ
(
d: MyNat
d
:
MyNat: Type
MyNat
) :
d: MyNat
d
+
1: MyNat
1
=
succ: MyNat → MyNat
succ
d: MyNat
d
:=
d: MyNat

d + 1 = succ d
d: MyNat

d + 1 = succ d
d: MyNat

d + 1 = d + 1

Goals 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.

lemma 
ne_succ_self: ∀ (n : MyNat), n ≠ succ n
ne_succ_self
(
n: MyNat
n
:
MyNat: Type
MyNat
) :
n: MyNat
n
succ: MyNat → MyNat
succ
n: MyNat
n
:=
n: MyNat

n succ n
n: MyNat

n succ n

zero
zero succ zero

Goals accomplished! 🐙
n: MyNat
ih: n succ n

succ
succ n succ (succ n)
n: MyNat
ih: n succ n
hs: succ n = succ (succ n)

succ
False
n: MyNat
ih: n succ n
hs: succ n = succ (succ n)

succ
n = succ n
n: MyNat
ih: n succ n
hs: succ n = succ (succ n)

succ.a
succ 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

  1. if your goal is ⊢ X ≠ Y then intro h will give you h : X = Y and a goal of ⊢ false. This is because X ≠ Y means (X = Y) → false. Conversely if your goal is false and you have h : X ≠ Y as a hypothesis then apply h will work backwards and turn the goal into X = Y.

  2. if hab : succ (3 * x + 2 * y + 1) = 0 is a hypothesis and your goal is ⊢ false, then exact succ_ne_zero _ hab will solve the goal, because Lean will figure out that _ is supposed to be 3 * x + 2 * y + 1.

Theorem

The product of two non-zero natural numbers is non-zero.

theorem 
mul_pos: ∀ (a b : MyNat), a ≠ 0 → b ≠ 0 → a * b ≠ 0
mul_pos
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
a: MyNat
a
0: MyNat
0
b: MyNat
b
0: MyNat
0
a: MyNat
a
*
b: MyNat
b
0: MyNat
0
:=
a, b: MyNat

a 0 b 0 a * b 0
a, b: MyNat
ha: a 0
hb: b 0

a * b 0
a, b: MyNat
ha: a 0
hb: b 0
hab: a * b = 0

False
a, b: MyNat
ha: a 0
hb: b 0
hab: a * b = 0

False
a: MyNat
ha: a 0
hb: zero 0
hab: a * zero = 0

zero
False
a: MyNat
ha: a 0
hb: zero 0
hab: a * zero = 0

zero
zero = 0

Goals accomplished! 🐙
a: MyNat
ha: a 0
b': MyNat
hb: succ b' 0
hab: a * succ b' = 0

succ
False
a: MyNat
ha: a 0
b': MyNat
hb: succ b' 0
hab: a * succ b' = 0

succ
False
a: MyNat
ha: a 0
b': MyNat
hb: succ b' 0
hab: a * b' + a = 0

succ
False
a: MyNat
ha: a 0
b': MyNat
hb: succ b' 0
hab: a * b' + a = 0

succ
False
a: MyNat
ha: a 0
b': MyNat
hb: succ b' 0
hab: a * b' + a = 0

succ
False
a: MyNat
ha: a 0
b': MyNat
hb: succ b' 0
hab: a * b' + a = 0

succ
a = 0
a: MyNat
ha: a 0
b': MyNat
hb: succ b' 0
hab: a * b' + a = 0

succ
a = 0
b': MyNat
hb: succ b' 0
ha: zero 0
hab: zero * b' + zero = 0

succ.zero
zero = 0

Goals accomplished! 🐙
b': MyNat
hb: succ b' 0
a': MyNat
ha: succ a' 0
hab: succ a' * b' + succ a' = 0

succ.succ
succ a' = 0
b': MyNat
hb: succ b' 0
a': MyNat
ha: succ a' 0
hab: succ a' * b' + succ a' = 0

succ.succ
succ a' = 0
b': MyNat
hb: succ b' 0
a': MyNat
ha: succ a' 0
hab: succ (succ a' * b' + a') = 0

succ.succ
succ a' = 0
b': MyNat
hb: succ b' 0
a': MyNat
ha: succ a' 0
hab: succ (succ a' * b' + a') = 0

succ.succ
succ a' = 0
b': MyNat
hb: succ b' 0
a': MyNat
ha: succ a' 0
hab: succ (succ a' * b' + a') = 0

succ.succ
succ a' = 0
b': MyNat
hb: succ b' 0
a': MyNat
ha: succ a' 0
hab: succ (succ a' * b' + a') = 0

succ.succ.h
False

Goals 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.

theorem 
eq_zero_or_eq_zero_of_mul_eq_zero: ∀ (a b : MyNat), a * b = 0 → a = 0 ∨ b = 0
eq_zero_or_eq_zero_of_mul_eq_zero
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) (
h: a * b = 0
h
:
a: MyNat
a
*
b: MyNat
b
=
0: MyNat
0
) :
a: MyNat
a
=
0: MyNat
0
b: MyNat
b
=
0: MyNat
0
:=
a, b: MyNat
h: a * b = 0

a = 0 b = 0
a, b: MyNat
h: a * b = 0

a = 0 b = 0
b: MyNat
h: zero * b = 0

zero
zero = 0 b = 0
b: MyNat
h: zero * b = 0

zero.h
zero = 0

Goals accomplished! 🐙
b, a': MyNat
h: succ a' * b = 0

succ
succ a' = 0 b = 0
b, a': MyNat
h: succ a' * b = 0

succ
succ a' = 0 b = 0
a': MyNat
h: succ a' * zero = 0

succ.zero
succ a' = 0 zero = 0
a': MyNat
h: succ a' * zero = 0

succ.zero.h
zero = 0

Goals accomplished! 🐙
a', b': MyNat
h: succ a' * succ b' = 0

succ.succ
succ a' = 0 succ b' = 0
a', b': MyNat
h: succ a' * succ b' = 0

succ.succ.h
False
a', b': MyNat
h: succ a' * succ b' = 0

succ.succ.h
False
a', b': MyNat
h: succ a' * b' + succ a' = 0

succ.succ.h
False
a', b': MyNat
h: succ a' * b' + succ a' = 0

succ.succ.h
False
a', b': MyNat
h: succ a' * b' + succ a' = 0

succ.succ.h
False
a', b': MyNat
h: succ a' * b' + succ a' = 0

succ.succ.h
False
a', b': MyNat
h: succ (succ a' * b' + a') = 0

succ.succ.h
False
a', b': MyNat
h: succ (succ a' * b' + a') = 0

succ.succ.h
False
a', b': MyNat
h: succ (succ a' * b' + a') = 0

succ.succ.h
False

Goals 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.

theorem 
mul_eq_zero_iff: ∀ (a b : MyNat), a * b = 0 ↔ a = 0 ∨ b = 0
mul_eq_zero_iff
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
):
a: MyNat
a
*
b: MyNat
b
=
0: MyNat
0
a: MyNat
a
=
0: MyNat
0
b: MyNat
b
=
0: MyNat
0
:=
a, b: MyNat

a * b = 0 a = 0 b = 0
a, b: MyNat

mp
a * b = 0 a = 0 b = 0
a, b: MyNat
a = 0 b = 0 a * b = 0
a, b: MyNat

mp
a * b = 0 a = 0 b = 0
a, b: MyNat
h: a * b = 0

mp
a = 0 b = 0

Goals accomplished! 🐙
a, b: MyNat

mpr
a = 0 b = 0 a * b = 0
a, b: MyNat

mpr
a = 0 b = 0 a * b = 0
a, b: MyNat
hab: a = 0 b = 0

mpr
a * b = 0
a, b: MyNat
hab: a = 0 b = 0

mpr
a * b = 0
a, b: MyNat
ha: a = 0

mpr.inl
a * b = 0
a, b: MyNat
ha: a = 0

mpr.inl
a * b = 0
a, b: MyNat
ha: a = 0

mpr.inl
0 * b = 0
a, b: MyNat
ha: a = 0

mpr.inl
0 * b = 0
a, b: MyNat
ha: a = 0

mpr.inl
0 * b = 0
a, b: MyNat
ha: a = 0

mpr.inl
0 = 0

Goals accomplished! 🐙
a, b: MyNat
hb: b = 0

mpr.inr
a * b = 0
a, b: MyNat
hb: b = 0

mpr.inr
a * b = 0
a, b: MyNat
hb: b = 0

mpr.inr
a * 0 = 0
a, b: MyNat
hb: b = 0

mpr.inr
a * 0 = 0
a, b: MyNat
hb: b = 0

mpr.inr
a * 0 = 0
a, b: MyNat
hb: b = 0

mpr.inr
0 = 0

Goals 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
theorem 
mul_left_cancel: ∀ (a b c : MyNat), a ≠ 0 → a * b = a * c → b = c
mul_left_cancel
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) (
ha: a ≠ 0
ha
:
a: MyNat
a
0: MyNat
0
) :
a: MyNat
a
*
b: MyNat
b
=
a: MyNat
a
*
c: MyNat
c
b: MyNat
b
=
c: MyNat
c
:=
a, b, c: MyNat
ha: a 0

a * b = a * c b = c
a, b, c: MyNat
ha: a 0

a * b = a * c b = c
a: MyNat
ha: a 0
b: MyNat

zero
a * b = a * zero b = zero
a: MyNat
ha: a 0
b: MyNat

zero
a * b = a * zero b = zero
a: MyNat
ha: a 0
b: MyNat

zero
a * b = a * 0 b = 0
a: MyNat
ha: a 0
b: MyNat

zero
a * b = a * 0 b = 0
a: MyNat
ha: a 0
b: MyNat

zero
a * b = a * 0 b = 0
a: MyNat
ha: a 0
b: MyNat

zero
a * b = 0 b = 0
a: MyNat
ha: a 0
b: MyNat

zero
a * b = 0 b = 0
a: MyNat
ha: a 0
b: MyNat
h: a * b = 0

zero
b = 0
a: MyNat
ha: a 0
b: MyNat
h: a * b = 0
x✝: a = 0 b = 0

zero
b = 0
a: MyNat
ha: a 0
b: MyNat
h: a * b = 0
h1: a = 0

zero.inl
b = 0
a: MyNat
ha: a 0
b: MyNat
h: a * b = 0
h1: a = 0

zero.inl.h
False
a: MyNat
ha: a 0
b: MyNat
h: a * b = 0
h1: a = 0

zero.inl.h
a = 0

Goals accomplished! 🐙
a: MyNat
ha: a 0
b: MyNat
h: a * b = 0
h2: b = 0

zero.inr
b = 0

Goals accomplished! 🐙
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
b: MyNat

succ
a * b = a * succ d b = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
b: MyNat
hb: a * b = a * succ d

succ
b = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
b: MyNat
hb: a * b = a * succ d

succ
b = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: a * zero = a * succ d

succ.zero
zero = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: a * zero = a * succ d

succ.zero
zero = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: a * 0 = a * succ d

succ.zero
zero = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: a * 0 = a * succ d

succ.zero
zero = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: a * 0 = a * succ d

succ.zero
zero = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: a * 0 = a * succ d

succ.zero
zero = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: 0 = a * succ d

succ.zero
zero = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: 0 = a * succ d

succ.zero
zero = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: 0 = a * succ d

succ.zero
zero = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: 0 = a * succ d

succ.zero
zero = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: 0 = a * succ d

succ.zero
0 = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: 0 = a * succ d

succ.zero
0 = succ d
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: 0 = a * succ d

succ.zero.h
False
a: MyNat
ha: a 0
d: MyNat
hd: (b : MyNat), a * b = a * d b = d
hb: 0 = a * succ d

succ.zero.h
a = 0
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

succ.zero.h
a = 0
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
x✝: a = 0 succ d = 0

succ.zero.h
a = 0
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: a = 0

succ.zero.h.inl
a = 0

Goals 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.inr
a = 0
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.inr.h
False

Goals 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.succ
succ c = succ d
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
h: Prop

succ.succ
succ c = succ d
Error: tactic 'apply' failed, failed to unify ?b = d with succ c = succ d case succ.succ 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 h : Prop succ c = succ d
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
h: Prop

succ.succ
succ c = succ d
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
h: Prop

succ.succ
succ 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.

lemma 
one_add_le_self: ∀ (x : MyNat), x ≤ 1 + x
one_add_le_self
(
x: MyNat
x
:
MyNat: Type
MyNat
) :
x: MyNat
x
1: MyNat
1
+
x: MyNat
x
:=
x: MyNat

x 1 + x
x: MyNat

x 1 + x
x: MyNat

c, 1 + x = x + c
x: MyNat

c, 1 + x = x + c
x: MyNat

1 + x = x + 1
x: MyNat

1 + x = x + 1
x: MyNat

x + 1 = x + 1

Goals 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.

lemma 
le_refl_mynat: ∀ (x : MyNat), x ≤ x
le_refl_mynat
(
x: MyNat
x
:
MyNat: Type
MyNat
) :
x: MyNat
x
x: MyNat
x
:=
x: MyNat

x x

Goals 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: 0 ≤ 0
example
: (
0: MyNat
0
:
MyNat: Type
MyNat
)
0: MyNat
0
:=

0 0

Goals 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.

theorem 
le_succ: ∀ (a b : MyNat), a ≤ b → a ≤ succ b
le_succ
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
a: MyNat
a
b: MyNat
b
a: MyNat
a
(
succ: MyNat → MyNat
succ
b: MyNat
b
) :=
a, b: MyNat

a b a succ b
a, b: MyNat
h: a b

a succ b
a, b: MyNat
h: a b

a succ b
a, b, c: MyNat
hc: b = a + c

intro
a succ b
a, b, c: MyNat
hc: b = a + c

intro
a succ b
a, b, c: MyNat
hc: b = a + c

intro
a succ (a + c)
a, b, c: MyNat
hc: b = a + c

intro
a 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.

lemma 
zero_le: ∀ (a : MyNat), 0 ≤ a
zero_le
(
a: MyNat
a
:
MyNat: Type
MyNat
) :
0: MyNat
0
a: MyNat
a
:=
a: MyNat

0 a
a: MyNat

a = 0 + a
a: MyNat

a = 0 + a
a: MyNat

a = a

Goals 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.

theorem 
le_trans: ∀ (a b c : MyNat), a ≤ b → b ≤ c → a ≤ c
le_trans
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) (
hab: a ≤ b
hab
:
a: MyNat
a
b: MyNat
b
) (
hbc: b ≤ c
hbc
:
b: MyNat
b
c: MyNat
c
) :
a: MyNat
a
c: MyNat
c
:=
a, b, c: MyNat
hab: a b
hbc: b c

a c
a, b, c: MyNat
hab: a b
hbc: b c

a c
a, b, c: MyNat
hbc: b c
d: MyNat
hd: b = a + d

intro
a c
a, b, c: MyNat
hbc: b c
d: MyNat
hd: b = a + d

intro
a c
a, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e

intro.intro
a c
a, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e

intro.intro
c = a + (d + e)
a, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e

intro.intro
c = a + (d + e)
a, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e

intro.intro
c = a + d + e
a, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e

intro.intro
c = a + d + e
a, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e

intro.intro
c = a + d + e
a, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e

intro.intro
c = b + e
a, b, c, d: MyNat
hd: b = a + d
e: MyNat
he: c = b + e

intro.intro
c = b + e

Goals 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.

theorem 
le_antisymm: ∀ (a b : MyNat), a ≤ b → b ≤ a → a = b
le_antisymm
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) (
hab: a ≤ b
hab
:
a: MyNat
a
b: MyNat
b
) (
hba: b ≤ a
hba
:
b: MyNat
b
a: MyNat
a
) :
a: MyNat
a
=
b: MyNat
b
:=
a, b: MyNat
hab: a b
hba: b a

a = b
a, b: MyNat
hab: a b
hba: b a

a = b
a, b: MyNat
hba: b a
c: MyNat
hc: b = a + c

intro
a = b
a, b: MyNat
hba: b a
c: MyNat
hc: b = a + c

intro
a = b
a, b, c: MyNat
hc: b = a + c
d: MyNat
hd: a = b + d

intro.intro
a = b
a, b, c: MyNat
hc: b = a + c
d: MyNat
hd: a = b + d

intro.intro
a = b
a, b, c: MyNat
hc: b = a + c
d: MyNat
hd: a = a + c + d

intro.intro
a = b
a, b, c: MyNat
hc: b = a + c
d: MyNat
hd: a = a + (c + d)

intro.intro
a = b
a, b, c: MyNat
hc: b = a + c
d: MyNat
hd: a = a + (c + d)

intro.intro
a = b
a, b, c: MyNat
hc: b = a + c
d: MyNat
hd: a = a + (c + d)

intro.intro
a = b
a, b, c: MyNat
hc: b = a + c
d: MyNat
hd✝: a = a + (c + d)
hd: a + (c + d) = a

intro.intro
a = b
a, b, c: MyNat
hc: b = a + c
d: MyNat
hd✝: a = a + (c + d)
hd: a + (c + d) = a
h: c + d = 0

intro.intro
a = b
a, 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.intro
a = b
a, 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.intro
a = b
a, 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.intro
a = b
a, 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.intro
a = b
a, 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.intro
a = b
a, 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.intro
a = b
a, 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.intro
a = a + 0
a, 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.intro
a = a + 0

Goals 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.

lemma 
le_zero: ∀ (a : MyNat), a ≤ 0 → a = 0
le_zero
(
a: MyNat
a
:
MyNat: Type
MyNat
) (
h: a ≤ 0
h
:
a: MyNat
a
0: MyNat
0
) :
a: MyNat
a
=
0: MyNat
0
:=
a: MyNat
h: a 0

a = 0
a: MyNat
h: a 0

a = 0
a, c: MyNat
hc: 0 = a + c

intro
a = 0
a, c: MyNat
hc✝: 0 = a + c
hc: a + c = 0

intro
a = 0

Goals 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.

lemma 
succ_le_succ: ∀ (a b : MyNat), a ≤ b → succ a ≤ succ b
succ_le_succ
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) (
h: a ≤ b
h
:
a: MyNat
a
b: MyNat
b
) :
succ: MyNat → MyNat
succ
a: MyNat
a
succ: MyNat → MyNat
succ
b: MyNat
b
:=
a, b: MyNat
h: a b

succ a succ b
a, b: MyNat
h: a b

succ a succ b
a, b, c: MyNat
hc: b = a + c

intro
succ a succ b
a, b, c: MyNat
hc: b = a + c

intro
succ b = succ a + c
a, b, c: MyNat
hc: b = a + c

intro
succ b = succ a + c
a, b, c: MyNat
hc: b = a + c

intro
succ (a + c) = succ a + c
a, b, c: MyNat
hc: b = a + c

intro
succ (a + c) = succ a + c
a, b, c: MyNat
hc: b = a + c

intro
succ (a + c) = succ a + c
a, b, c: MyNat
hc: b = a + c

intro
succ (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.

theorem 
le_total: ∀ (a b : MyNat), a ≤ b ∨ b ≤ a
le_total
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
a: MyNat
a
b: MyNat
b
b: MyNat
b
a: MyNat
a
:=
a, b: MyNat

a b b a
b: MyNat

(a : MyNat), a b b a
b: MyNat

(a : MyNat), a b b a

zero
(a : MyNat), a zero zero a
a: MyNat

zero
a zero zero a
a: MyNat

zero.h
zero a

Goals accomplished! 🐙
d: MyNat
hd: (a : MyNat), a d d a

succ
(a : MyNat), a succ d succ d a
d: MyNat
hd: (a : MyNat), a d d a
a: MyNat

succ
a succ d succ d a
d: MyNat
hd: (a : MyNat), a d d a
a: MyNat

succ
a succ d succ d a
d: MyNat
hd: (a : MyNat), a d d a

succ.zero
zero succ d succ d zero
d: MyNat
hd: (a : MyNat), a d d a

succ.zero.h
zero succ d

Goals accomplished! 🐙
d: MyNat
hd: (a : MyNat), a d d a
a: MyNat

succ.succ
succ a succ d succ d succ a
d: MyNat
hd: (a : MyNat), a d d a
a: MyNat
h2: a d d a

succ.succ
succ a succ d succ d succ a
d: MyNat
hd: (a : MyNat), a d d a
a: MyNat
h2: a d d a

succ.succ
succ a succ d succ d succ a
d: MyNat
hd: (a : MyNat), a d d a
a: MyNat
h: a d

succ.succ.inl
succ a succ d succ d succ a
d: MyNat
hd: (a : MyNat), a d d a
a: MyNat
h: a d

succ.succ.inl.h
succ a succ d

Goals accomplished! 🐙
d: MyNat
hd: (a : MyNat), a d d a
a: MyNat
h: d a

succ.succ.inr
succ a succ d succ d succ a
d: MyNat
hd: (a : MyNat), a d d a
a: MyNat
h: d a

succ.succ.inr.h
succ d succ a

Goals 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.

lemma 
le_succ_self: ∀ (a : MyNat), a ≤ succ a
le_succ_self
(
a: MyNat
a
:
MyNat: Type
MyNat
) :
a: MyNat
a
succ: MyNat → MyNat
succ
a: MyNat
a
:=
a: MyNat

a succ a

Goals 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.

theorem 
add_le_add_right: ∀ {a b : MyNat}, a ≤ b → ∀ (t : MyNat), a + t ≤ b + t
add_le_add_right
{
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
} :
a: MyNat
a
b: MyNat
b
t: MyNat
t
, (
a: MyNat
a
+
t: MyNat
t
) (
b: MyNat
b
+
t: MyNat
t
) :=
a, b: MyNat

a b (t : MyNat), a + t b + t
a, b: MyNat
h: a b

(t : MyNat), a + t b + t
a, b: MyNat
h: a b

(t : MyNat), a + t b + t
a, b, c: MyNat
hc: b = a + c

intro
(t : MyNat), a + t b + t
a, b, c: MyNat
hc: b = a + c
t: MyNat

intro
a + t b + t
a, b, c: MyNat
hc: b = a + c
t: MyNat

intro
b + t = a + t + c
a, b, c: MyNat
hc: b = a + c
t: MyNat

intro
b + t = a + t + c
a, b, c: MyNat
hc: b = a + c
t: MyNat

intro
a + c + t = a + t + c
a, b, c: MyNat
hc: b = a + c
t: MyNat

intro
a + c + t = a + t + c
a, b, c: MyNat
hc: b = a + c
t: MyNat

intro
a + c + t = a + t + c
a, b, c: MyNat
hc: b = a + c
t: MyNat

intro
a + t + c = a + t + c

Goals 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.

theorem 
le_of_succ_le_succ: ∀ (a b : MyNat), succ a ≤ succ b → a ≤ b
le_of_succ_le_succ
(
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
:=
a, b: MyNat

succ a succ b a b
a, b: MyNat
h: succ a succ b

a b
a, b: MyNat
h: succ a succ b

a b
a, b, c: MyNat
hc: succ b = succ a + c

intro
a b
a, b, c: MyNat
hc: succ b = succ a + c

intro
b = a + c
a, b, c: MyNat
hc: succ b = succ a + c

intro.a
succ b = succ (a + c)
a, b, c: MyNat
hc: succ b = succ a + c

intro.a
succ b = succ (a + c)
a, b, c: MyNat
hc: succ b = succ a + c

intro.a
succ a + c = succ (a + c)
a, b, c: MyNat
hc: succ b = succ a + c

intro.a
succ 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.

theorem 
not_succ_le_self: ∀ (a : MyNat), ¬succ a ≤ a
not_succ_le_self
(
a: MyNat
a
:
MyNat: Type
MyNat
) : ¬ (
succ: MyNat → MyNat
succ
a: MyNat
a
a: MyNat
a
) :=
a: MyNat

¬succ a a
a: MyNat
h: succ a a

False
a: MyNat
h: succ a a

False
a, c: MyNat
h: a = succ a + c

intro
False
a, c: MyNat
h: a = succ a + c

intro
False
c: MyNat
h: zero = succ zero + c

intro.zero
False
c: MyNat
h: zero = succ zero + c

intro.zero
False
c: MyNat
h: zero = succ (zero + c)

intro.zero
False
c: MyNat
h: zero = succ (zero + c)

intro.zero
False
c: MyNat
h: zero = succ (zero + c)

intro.zero
False

Goals accomplished! 🐙
c, d: MyNat
hd: d = succ d + c False
h: succ d = succ (succ d) + c

intro.succ
False
c, d: MyNat
hd: d = succ d + c False
h: succ d = succ (succ d) + c

intro.succ
False
c, d: MyNat
hd: d = succ d + c False
h: succ d = succ (succ d + c)

intro.succ
False
c, d: MyNat
hd: d = succ d + c False
h: succ d = succ (succ d + c)

intro.succ
False
c, d: MyNat
hd: d = succ d + c False
h: succ d = succ (succ d + c)

intro.succ
False
c, d: MyNat
hd: d = succ d + c False
h: succ d = succ (succ d + c)

intro.succ
d = succ d + c
c, d: MyNat
hd: d = succ d + c False
h: succ d = succ (succ d + c)

intro.succ.a
succ 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.

theorem 
add_le_add_left: ∀ {a b : MyNat}, a ≤ b → ∀ (t : MyNat), t + a ≤ t + b
add_le_add_left
{
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
} (
h: a ≤ b
h
:
a: MyNat
a
b: MyNat
b
) (
t: MyNat
t
:
MyNat: Type
MyNat
) :
t: MyNat
t
+
a: MyNat
a
t: MyNat
t
+
b: MyNat
b
:=
a, b: MyNat
h: a b
t: MyNat

t + a t + b
a, b: MyNat
h: a b
t: MyNat

t + a t + b
a, b, t, c: MyNat
hc: b = a + c

intro
t + a t + b
a, b, t, c: MyNat
hc: b = a + c

intro
t + b = t + a + c
a, b, t, c: MyNat
hc: b = a + c

intro
t + b = t + a + c
a, b, t, c: MyNat
hc: b = a + c

intro
t + (a + c) = t + a + c
a, b, t, c: MyNat
hc: b = a + c

intro
t + (a + c) = t + a + c
a, b, t, c: MyNat
hc: b = a + c

intro
t + (a + c) = t + a + c
a, b, t, c: MyNat
hc: b = a + c

intro
t + (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.

lemma 
lt_aux₁: ∀ (a b : MyNat), a ≤ b ∧ ¬b ≤ a → succ a ≤ b
lt_aux₁
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
a: MyNat
a
b: MyNat
b
¬ (
b: MyNat
b
a: MyNat
a
)
succ: MyNat → MyNat
succ
a: MyNat
a
b: MyNat
b
:=
a, b: MyNat

a b ¬b a succ a b
a, b: MyNat
h: a b ¬b a

succ a b
a, b: MyNat
h: a b ¬b a

succ a b
a, b: MyNat
h1: a b
h2: ¬b a

intro
succ a b
a, b: MyNat
h1: a b
h2: ¬b a

intro
succ a b
a, b: MyNat
h2: ¬b a
c: MyNat
hc: b = a + c

intro.intro
succ a b
a, b: MyNat
h2: ¬b a
c: MyNat
hc: b = a + c

intro.intro
succ a b
a, b: MyNat
h2: ¬b a
hc: b = a + zero

intro.intro.zero
succ a b
a, b: MyNat
h2: ¬b a
hc: b = a + zero

intro.intro.zero.h
False
a, b: MyNat
h2: ¬b a
hc: b = a + zero

intro.intro.zero.h
False
a, b: MyNat
h2: ¬b a
hc: b = a + 0

intro.intro.zero.h
False
a, b: MyNat
h2: ¬b a
hc: b = a

intro.intro.zero.h
False
a, b: MyNat
h2: ¬b a
hc: b = a

intro.intro.zero.h
False
a, b: MyNat
h2: ¬b a
hc: b = a

intro.intro.zero.h
False
a, b: MyNat
h2: ¬b a
hc: b = a

intro.intro.zero.h
b a
a, b: MyNat
h2: ¬b a
hc: b = a

intro.intro.zero.h
b a
a, b: MyNat
h2: ¬b a
hc: b = a

intro.intro.zero.h
a a

Goals accomplished! 🐙
a, b: MyNat
h2: ¬b a
d: MyNat
hc: b = a + succ d

intro.intro.succ
succ a b
a, b: MyNat
h2: ¬b a
d: MyNat
hc: b = a + succ d

intro.intro.succ
b = succ a + d
a, b: MyNat
h2: ¬b a
d: MyNat
hc: b = a + succ d

intro.intro.succ
b = succ a + d
a, b: MyNat
h2: ¬b a
d: MyNat
hc: b = a + succ d

intro.intro.succ
a + succ d = succ a + d
a, b: MyNat
h2: ¬b a
d: MyNat
hc: b = a + succ d

intro.intro.succ
a + succ d = succ a + d
a, b: MyNat
h2: ¬b a
d: MyNat
hc: b = a + succ d

intro.intro.succ
a + succ d = succ a + d
a, b: MyNat
h2: ¬b a
d: MyNat
hc: b = a + succ d

intro.intro.succ
succ (a + d) = succ a + d
a, b: MyNat
h2: ¬b a
d: MyNat
hc: b = a + succ d

intro.intro.succ
succ (a + d) = succ a + d
a, b: MyNat
h2: ¬b a
d: MyNat
hc: b = a + succ d

intro.intro.succ
succ (a + d) = succ a + d
a, b: MyNat
h2: ¬b a
d: MyNat
hc: b = a + succ d

intro.intro.succ
succ (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).

lemma 
lt_aux₂: ∀ (a b : MyNat), succ a ≤ b → a ≤ b ∧ ¬b ≤ a
lt_aux₂
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
succ: MyNat → MyNat
succ
a: MyNat
a
b: MyNat
b
a: MyNat
a
b: MyNat
b
¬ (
b: MyNat
b
a: MyNat
a
) :=
a, b: MyNat

succ a b a b ¬b a
a, b: MyNat
h: succ a b

a b ¬b a
a, b: MyNat
h: succ a b

left
a b
a, b: MyNat
h: succ a b
¬b a
a, b: MyNat
h: succ a b

left
a b
a, b: MyNat
h: succ a b

left.hab
a succ a
a, b: MyNat
h: succ a b
succ a b
a, b: MyNat
h: succ a b

left.hbc
succ a b

Goals accomplished! 🐙
a, b: MyNat
h: succ a b

right
¬b a
a, b: MyNat
h: succ a b

right
¬b a
a, b: MyNat
h: succ a b
nh: b a

right
False
a, b: MyNat
h: succ a b
nh: b a

right
a = succ a
a, b: MyNat
h: succ a b
nh: b a

right.hab
a succ a
a, b: MyNat
h: succ a b
nh: b a
succ a a
a, b: MyNat
h: succ a b
nh: b a

right.hba
succ a a

Goals 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.

lemma 
lt_iff_succ_le: ∀ (a b : MyNat), a < b ↔ succ a ≤ b
lt_iff_succ_le
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
a: MyNat
a
<
b: MyNat
b
succ: MyNat → MyNat
succ
a: MyNat
a
b: MyNat
b
:=
a, b: MyNat

a < b succ a b
a, b: MyNat

mp
a < b succ a b
a, b: MyNat
succ a b a < b
a, b: MyNat

mpr
succ a b a < b

Goals 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

lemma 
lt_irrefl: ∀ (a : MyNat), ¬a < a
lt_irrefl
(
a: MyNat
a
:
MyNat: Type
MyNat
) : ¬ (
a: MyNat
a
<
a: MyNat
a
) :=
a: MyNat

¬a < a
a: MyNat
h: a < a

False
a: MyNat
h: a < a

False
a: MyNat
h1: a a
h2: ¬a a

intro
False
a: MyNat
h1: a a
h2: ¬a a

intro
a a

Goals accomplished! 🐙
lemma
ne_of_lt: ∀ (a b : MyNat), a < b → a ≠ b
ne_of_lt
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
a: MyNat
a
<
b: MyNat
b
a: MyNat
a
b: MyNat
b
:=
a, b: MyNat

a < b a b
a, b: MyNat
h: a < b

a b
a, b: MyNat
h: a < b
h1: a = b

False
a, b: MyNat
h: a < b
h1: a = b

False
a, b: MyNat
h1: a = b
h2: a b
h3: ¬b a

intro
False
a, b: MyNat
h1: a = b
h2: a b
h3: ¬b a

intro
b a
a, b: MyNat
h1: a = b
h2: a b
h3: ¬b a

intro
b a
a, b: MyNat
h1: a = b
h2: a b
h3: ¬b a

intro
b b

Goals accomplished! 🐙
theorem
not_lt_zero: ∀ (a : MyNat), ¬a < 0
not_lt_zero
(
a: MyNat
a
:
MyNat: Type
MyNat
) : ¬(
a: MyNat
a
<
0: MyNat
0
) :=
a: MyNat

¬a < 0
a: MyNat
h: a < 0

False
a: MyNat
h: a < 0

False
a: MyNat
ha: a 0
hna: ¬0 a

intro
False
a: MyNat
ha: a 0
hna: ¬0 a

intro
0 a

Goals accomplished! 🐙
theorem
lt_of_lt_of_le: ∀ (a b c : MyNat), a < b → b ≤ c → a < c
lt_of_lt_of_le
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) :
a: MyNat
a
<
b: MyNat
b
b: MyNat
b
c: MyNat
c
a: MyNat
a
<
c: MyNat
c
:=
a, b, c: MyNat

a < b b c a < c
a, b, c: MyNat
hab: a < b

b c a < c
a, b, c: MyNat
hab: a < b
hbc: b c

a < c
a, b, c: MyNat
hab: a < b
hbc: b c

a < c
a, b, c: MyNat
hab: succ a b
hbc: b c

succ a c
a, b, c: MyNat
hab: succ a b
hbc: b c

succ a c
a, b, c: MyNat
hab: succ a b
hbc: b c

succ a c
a, b, c: MyNat
hab: succ a b
hbc: b c

succ a c
a, b, c: MyNat
hab: succ a b
x: MyNat
hx: c = b + x

intro
succ a c
a, b, c: MyNat
hab: succ a b
x: MyNat
hx: c = b + x

intro
succ a c
a, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a c
a, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a c
a, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a b + x
a, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a b + x
a, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a b + x
a, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a succ a + y + x
a, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a succ a + y + x
a, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a + y + x = succ a + (y + x)
a, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a + y + x = succ a + (y + x)
a, b, c, x: MyNat
hx: c = b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a + (y + x) = succ a + (y + x)

Goals accomplished! 🐙
theorem
lt_of_le_of_lt: ∀ (a b c : MyNat), a ≤ b → b < c → a < c
lt_of_le_of_lt
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) :
a: MyNat
a
b: MyNat
b
b: MyNat
b
<
c: MyNat
c
a: MyNat
a
<
c: MyNat
c
:=
a, b, c: MyNat

a b b < c a < c
a, b, c: MyNat
hab: a b

b < c a < c
a, b, c: MyNat
hab: a b
hbc: b < c

a < c
a, b, c: MyNat
hab: a b
hbc: b < c

a < c
a, b, c: MyNat
hab: a b
hbc: succ b c

succ a c
a, b, c: MyNat
hab: a b
hbc: succ b c

succ a c
a, b, c: MyNat
hab: a b
hbc: succ b c

succ a c
a, b, c: MyNat
hab: a b
hbc: succ b c

succ a c
a, b, c: MyNat
hab: a b
x: MyNat
hx: c = succ b + x

intro
succ a c
a, b, c: MyNat
hab: a b
x: MyNat
hx: c = succ b + x

intro
succ a c
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ a c
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ a c
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ a succ b + x
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ a succ b + x
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ a succ b + x
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ a succ (a + y) + x
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ a succ (a + y) + x
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ (a + y) + x = succ a + (y + x)
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ (a + y) + x = succ a + (y + x)
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ (a + y + x) = succ a + (y + x)
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ (a + y + x) = succ a + (y + x)
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ (a + y + x) = succ a + (y + x)
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ (a + y + x) = succ (a + (y + x))
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ (a + y + x) = succ (a + (y + x))
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ (a + y + x) = succ (a + (y + x))
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = a + y

intro.intro
succ (a + (y + x)) = succ (a + (y + x))

Goals accomplished! 🐙
theorem
lt_trans: ∀ (a b c : MyNat), a < b → b < c → a < c
lt_trans
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) :
a: MyNat
a
<
b: MyNat
b
b: MyNat
b
<
c: MyNat
c
a: MyNat
a
<
c: MyNat
c
:=
a, b, c: MyNat

a < b b < c a < c
a, b, c: MyNat
hab: a < b

b < c a < c
a, b, c: MyNat
hab: a < b
hbc: b < c

a < c
a, b, c: MyNat
hab: a < b
hbc: b < c

a < c
a, b, c: MyNat
hab: succ a b
hbc: succ b c

succ a c
a, b, c: MyNat
hab: succ a b
hbc: succ b c

succ a c
a, b, c: MyNat
hab: succ a b
hbc: succ b c

succ a c
a, b, c: MyNat
hab: succ a b
hbc: succ b c

succ a c
a, b, c: MyNat
hab: succ a b
x: MyNat
hx: c = succ b + x

intro
succ a c
a, b, c: MyNat
hab: succ a b
x: MyNat
hx: c = succ b + x

intro
succ a c
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a c
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a c
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a succ b + x
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a succ b + x
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a succ b + x
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a succ (succ a + y) + x
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ a succ (succ a + y) + x
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y

intro.intro
succ (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.intro
succ (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.intro
succ (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.intro
succ (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.intro
succ (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.intro
succ (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.intro
a + y + x + 1 + 1 = a + (y + x + 1) + 1
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y

intro.intro
a + y + x + 1 + 1 = a + (y + x + 1) + 1
a, b, c, x: MyNat
hx: c = succ b + x
y: MyNat
hy: b = succ a + y

intro.intro
a + y + x + 1 + 1 = succ (a + (y + x + 1))

Goals accomplished! 🐙
theorem
lt_iff_le_and_ne: ∀ (a b : MyNat), a < b ↔ a ≤ b ∧ a ≠ b
lt_iff_le_and_ne
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
a: MyNat
a
<
b: MyNat
b
a: MyNat
a
b: MyNat
b
a: MyNat
a
b: MyNat
b
:=
a, b: MyNat

a < b a b a b
a, b: MyNat

mp
a < b a b a b
a, b: MyNat
a b a b a < b
a, b: MyNat

mp
a < b a b a b
a, b: MyNat
h: a < b

mp
a b a b
a, b: MyNat
h: a < b

mp
a b a b
a, b: MyNat
h1: a b
h2: ¬b a

mp.intro
a b a b
a, b: MyNat
h1: a b
h2: ¬b a

mp.intro.left
a b
a, b: MyNat
h1: a b
h2: ¬b a
a b
a, b: MyNat
h1: a b
h2: ¬b a

mp.intro.right
a b
a, b: MyNat
h1: a b
h2: ¬b a
h: a = b

mp.intro.right
False
a, b: MyNat
h1: a b
h2: ¬b a
h: a = b

mp.intro.right
b a
a, b: MyNat
h1: a b
h2: ¬b a
h: a = b

mp.intro.right
b a
a, b: MyNat
h1: a b
h2: ¬b a
h: a = b

mp.intro.right
b b

Goals accomplished! 🐙
a, b: MyNat

mpr
a b a b a < b
a, b: MyNat

mpr
a b a b a < b
a, b: MyNat
h: a b a b

mpr
a < b
a, b: MyNat
h: a b a b

mpr
a < b
a, b: MyNat
h1: a b
h2: a b

mpr.intro
a < b
a, b: MyNat
h1: a b
h2: a b

mpr.intro.left
a b
a, b: MyNat
h1: a b
h2: a b
¬b a
a, b: MyNat
h1: a b
h2: a b

mpr.intro.right
¬b a
a, b: MyNat
h1: a b
h2: a b
h: b a

mpr.intro.right
False
a, b: MyNat
h1: a b
h2: a b
h: b a

mpr.intro.right
a = b

Goals accomplished! 🐙

Goals accomplished! 🐙
theorem
lt_succ_self: ∀ (n : MyNat), n < succ n
lt_succ_self
(
n: MyNat
n
:
MyNat: Type
MyNat
) :
n: MyNat
n
<
succ: MyNat → MyNat
succ
n: MyNat
n
:=
n: MyNat

n < succ n
n: MyNat

n < succ n
n: MyNat

n succ n n succ n
n: MyNat

n succ n n succ n
n: MyNat

left
n succ n
n: MyNat
n succ n
n: MyNat

left
n succ n

Goals accomplished! 🐙
n: MyNat

right
n succ n
n: MyNat

right
n succ n
n: MyNat
h: n = succ n

right
False

Goals accomplished! 🐙

Goals accomplished! 🐙
lemma
succ_le_succ_iff: ∀ (m n : MyNat), succ m ≤ succ n ↔ m ≤ n
succ_le_succ_iff
(
m: MyNat
m
n: MyNat
n
:
MyNat: Type
MyNat
) :
succ: MyNat → MyNat
succ
m: MyNat
m
succ: MyNat → MyNat
succ
n: MyNat
n
m: MyNat
m
n: MyNat
n
:=
m, n: MyNat

succ m succ n m n
m, n: MyNat

mp
succ m succ n m n
m, n: MyNat
m n succ m succ n
m, n: MyNat

mp
succ m succ n m n
m, n: MyNat
h: succ m succ n

mp
m n
m, n: MyNat
h: succ m succ n

mp
m n
m, n, c: MyNat
hc: succ n = succ m + c

mp.intro
m n
m, n, c: MyNat
hc: succ n = succ m + c

mp.intro
n = m + c
m, n, c: MyNat
hc: succ n = succ m + c

mp.intro.a
succ n = succ (m + c)
m, n, c: MyNat
hc: succ n = succ m + c

mp.intro.a
succ n = succ (m + c)
m, n, c: MyNat
hc: succ n = succ m + c

mp.intro.a
succ m + c = succ (m + c)
m, n, c: MyNat
hc: succ n = succ m + c

mp.intro.a
succ m + c = succ (m + c)
m, n, c: MyNat
hc: succ n = succ m + c

mp.intro.a
succ m + c = succ (m + c)
m, n, c: MyNat
hc: succ n = succ m + c

mp.intro.a
succ (m + c) = succ (m + c)

Goals accomplished! 🐙
m, n: MyNat

mpr
m n succ m succ n
m, n: MyNat

mpr
m n succ m succ n
m, n: MyNat
h: m n

mpr
succ m succ n
m, n: MyNat
h: m n

mpr
succ m succ n
m, n, c: MyNat
hc: n = m + c

mpr.intro
succ m succ n
m, n, c: MyNat
hc: n = m + c

mpr.intro
succ n = succ m + c
m, n, c: MyNat
hc: n = m + c

mpr.intro
succ n = succ m + c
m, n, c: MyNat
hc: n = m + c

mpr.intro
succ (m + c) = succ m + c
m, n, c: MyNat
hc: n = m + c

mpr.intro
succ (m + c) = succ m + c
m, n, c: MyNat
hc: n = m + c

mpr.intro
succ (m + c) = succ m + c
m, n, c: MyNat
hc: n = m + c

mpr.intro
succ (m + c) = succ (m + c)

Goals accomplished! 🐙

Goals accomplished! 🐙
lemma
lt_succ_iff_le: ∀ (m n : MyNat), m < succ n ↔ m ≤ n
lt_succ_iff_le
(
m: MyNat
m
n: MyNat
n
:
MyNat: Type
MyNat
) :
m: MyNat
m
<
succ: MyNat → MyNat
succ
n: MyNat
n
m: MyNat
m
n: MyNat
n
:=
m, n: MyNat

m < succ n m n
m, n: MyNat

m < succ n m n
m, n: MyNat

succ m succ n m n
m, n: MyNat

succ m succ n m n

Goals accomplished! 🐙
lemma
le_of_add_le_add_left: ∀ (a b c : MyNat), a + b ≤ a + c → b ≤ c
le_of_add_le_add_left
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) :
a: MyNat
a
+
b: MyNat
b
a: MyNat
a
+
c: MyNat
c
b: MyNat
b
c: MyNat
c
:=
a, b, c: MyNat

a + b a + c b c
a, b, c: MyNat
h: a + b a + c

b c
a, b, c: MyNat
h: a + b a + c

b c
a, b, c, d: MyNat
hd: a + c = a + b + d

intro
b c
a, b, c, d: MyNat
hd: a + c = a + b + d

intro
c = b + d
a, b, c, d: MyNat
hd: a + c = a + b + d

intro.a
a + c = a + (b + d)
a, b, c, d: MyNat
hd: a + c = a + b + d

intro.a
a + c = a + (b + d)
a, b, c, d: MyNat
hd: a + c = a + b + d

intro.a
a + b + d = a + (b + d)
a, b, c, d: MyNat
hd: a + c = a + b + d

intro.a
a + b + d = a + (b + d)
a, b, c, d: MyNat
hd: a + c = a + b + d

intro.a
a + b + d = a + (b + d)
a, b, c, d: MyNat
hd: a + c = a + b + d

intro.a
a + (b + d) = a + (b + d)

Goals accomplished! 🐙
lemma
lt_of_add_lt_add_left: ∀ (a b c : MyNat), a + b < a + c → b < c
lt_of_add_lt_add_left
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) :
a: MyNat
a
+
b: MyNat
b
<
a: MyNat
a
+
c: MyNat
c
b: MyNat
b
<
c: MyNat
c
:=
a, b, c: MyNat

a + b < a + c b < c
a, b, c: MyNat

a + b < a + c b < c
a, b, c: MyNat

succ (a + b) a + c b < c
a, b, c: MyNat

succ (a + b) a + c b < c
a, b, c: MyNat

succ (a + b) a + c b < c
a, b, c: MyNat
h: succ (a + b) a + c

succ b c
a, b, c: MyNat
h: succ (a + b) a + c

a
a + succ b a + c
a, b, c: MyNat
h: succ (a + b) a + c

a
a + succ b a + c
a, b, c: MyNat
h: succ (a + b) a + c

a
succ (a + b) a + c
a, b, c: MyNat
h: succ (a + b) a + c

a
succ (a + b) a + c

Goals accomplished! 🐙
lemma
add_lt_add_right: ∀ (a b : MyNat), a < b → ∀ (c : MyNat), a + c < b + c
add_lt_add_right
(
a: MyNat
a
b: MyNat
b
:
MyNat: Type
MyNat
) :
a: MyNat
a
<
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
,
a: MyNat
a
+
c: MyNat
c
<
b: MyNat
b
+
c: MyNat
c
:=
a, b: MyNat

a < b (c : MyNat), a + c < b + c
a, b: MyNat
h: a < b

(c : MyNat), a + c < b + c
a, b: MyNat
h: a < b
c: MyNat

a + c < b + c
a, b: MyNat
h: a < b
c: MyNat

a + c < b + c
a, b: MyNat
h: succ a b
c: MyNat

succ (a + c) b + c
a, b: MyNat
h: succ a b
c: MyNat

succ (a + c) b + c
a, b: MyNat
h: succ a b
c: MyNat

succ (a + c) b + c
a, b: MyNat
h: succ a b
c: MyNat

succ (a + c) b + c
a, b, c, d: MyNat
hd: b = succ a + d

intro
succ (a + c) b + c
a, b, c, d: MyNat
hd: b = succ a + d

intro
b + c = succ (a + c) + d
a, b, c, d: MyNat
hd: b = succ a + d

intro
b + c = succ (a + c) + d
a, b, c, d: MyNat
hd: b = succ a + d

intro
succ a + d + c = succ (a + c) + d
a, b, c, d: MyNat
hd: b = succ a + d

intro
succ a + d + c = succ (a + c) + d
a, b, c, d: MyNat
hd: b = succ a + d

intro
succ a + d + c = succ (a + c) + d
a, b, c, d: MyNat
hd: b = succ a + d

intro
succ a + d + c = succ (a + c) + d
a, b, c, d: MyNat
hd: b = succ a + d

intro
succ (a + d + c) = succ (a + c + d)
a, b, c, d: MyNat
hd: b = succ a + d

intro
succ (a + d + c) = succ (a + c) + d
a, b, c, d: MyNat
hd: b = succ a + d

intro
succ (a + d + c) = succ (a + c + d)
a, b, c, d: MyNat
hd: b = succ a + d

intro
succ (a + c + d) = succ (a + c + d)

Goals accomplished! 🐙
-- 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} def
succ_lt_succ_iff: ∀ (a b : MyNat), succ a < succ b ↔ a < b
succ_lt_succ_iff
(
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
:=
a, b: MyNat

succ a < succ b a < b
a, b: MyNat

succ a < succ b a < b
a, b: MyNat

succ a < succ b a < b
a, b: MyNat

succ (succ a) succ b succ a b
a, b: MyNat

succ (succ a) succ b a < b

Goals accomplished! 🐙
-- multiplication theorem
mul_le_mul_of_nonneg_left: ∀ (a b c : MyNat), a ≤ b → 0 ≤ c → c * a ≤ c * b
mul_le_mul_of_nonneg_left
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) :
a: MyNat
a
b: MyNat
b
0: MyNat
0
c: MyNat
c
c: MyNat
c
*
a: MyNat
a
c: MyNat
c
*
b: MyNat
b
:=
a, b, c: MyNat

a b 0 c c * a c * b
a, b, c: MyNat
hab: a b

0 c c * a c * b
a, b, c: MyNat
hab: a b

0 c c * a c * b
Warning: unused variable `h0` [linter.unusedVariables]
a, b, c: MyNat
hab: a b
h0: 0 c

c * a c * b
a, b, c: MyNat
hab: a b
h0: 0 c

c * a c * b
a, b, c: MyNat
h0: 0 c
d: MyNat
hd: b = a + d

intro
c * a c * b
a, b, c: MyNat
h0: 0 c
d: MyNat
hd: b = a + d

intro
c * a c * b
a, b, c: MyNat
h0: 0 c
d: MyNat
hd: b = a + d

intro
c * a c * (a + d)
a, b, c: MyNat
h0: 0 c
d: MyNat
hd: b = a + d

intro
c * a c * (a + d)
a, b, c: MyNat
h0: 0 c
d: MyNat
hd: b = a + d

intro
c * a c * (a + d)
a, b, c: MyNat
h0: 0 c
d: MyNat
hd: b = a + d

intro
c * a c * a + c * d
a, b, c: MyNat
h0: 0 c
d: MyNat
hd: b = a + d

intro
c * a c * a + c * d

Goals accomplished! 🐙
theorem
mul_le_mul_of_nonneg_right: ∀ (a b c : MyNat), a ≤ b → 0 ≤ c → a * c ≤ b * c
mul_le_mul_of_nonneg_right
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) :
a: MyNat
a
b: MyNat
b
0: MyNat
0
c: MyNat
c
a: MyNat
a
*
c: MyNat
c
b: MyNat
b
*
c: MyNat
c
:=
a, b, c: MyNat

a b 0 c a * c b * c
a, b, c: MyNat
hab: a b

0 c a * c b * c
a, b, c: MyNat
hab: a b
h0: 0 c

a * c b * c
a, b, c: MyNat
hab: a b
h0: 0 c

a * c b * c
a, b, c: MyNat
hab: a b
h0: 0 c

c * a b * c
a, b, c: MyNat
hab: a b
h0: 0 c

c * a b * c
a, b, c: MyNat
hab: a b
h0: 0 c

c * a b * c
a, b, c: MyNat
hab: a b
h0: 0 c

c * a c * b
a, b, c: MyNat
hab: a b
h0: 0 c

c * a c * b
a, b, c: MyNat
hab: a b
h0: 0 c

a
a b
a, b, c: MyNat
hab: a b
h0: 0 c
0 c
a, b, c: MyNat
hab: a b
h0: 0 c

a
0 c

Goals accomplished! 🐙
theorem
mul_lt_mul_of_pos_left: ∀ (a b c : MyNat), a < b → 0 < c → c * a < c * b
mul_lt_mul_of_pos_left
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) :
a: MyNat
a
<
b: MyNat
b
0: MyNat
0
<
c: MyNat
c
c: MyNat
c
*
a: MyNat
a
<
c: MyNat
c
*
b: MyNat
b
:=
a, b, c: MyNat

a < b 0 < c c * a < c * b
a, b, c: MyNat
hab: a < b

0 < c c * a < c * b
a, b, c: MyNat
hab: a < b
hc: 0 < c

c * a < c * b
a, b, c: MyNat
hab: a < b
hc: 0 < c

c * a < c * b
a, b: MyNat
hab: a < b
hc: 0 < zero

zero
zero * a < zero * b
a, b: MyNat
hab: a < b
hc: 0 < zero

zero.h
False

Goals accomplished! 🐙
a, b: MyNat
hab: a < b
d: MyNat
hc: 0 < succ d

succ
succ d * a < succ d * b
a, b: MyNat
hab: a < b
d: MyNat

succ
succ d * a < succ d * b
a, b: MyNat
hab: a < b
d: MyNat

succ
succ d * a < succ d * b
a, b: MyNat
hab: a < b

succ.zero
succ zero * a < succ zero * b
a, b: MyNat
hab: a < b

succ.zero
succ zero * a < succ zero * b
a, b: MyNat
hab: a < b

succ.zero
zero * a + a < succ zero * b
a, b: MyNat
hab: a < b

succ.zero
0 * a + a < succ 0 * b
a, b: MyNat
hab: a < b

succ.zero
0 + a < succ 0 * b
a, b: MyNat
hab: a < b

succ.zero
0 + a < succ 0 * b
a, b: MyNat
hab: a < b

succ.zero
0 + a < succ 0 * b
a, b: MyNat
hab: a < b

succ.zero
a < succ 0 * b
a, b: MyNat
hab: a < b

succ.zero
a < 0 * b + b
a, b: MyNat
hab: a < b

succ.zero
a < 0 + b
a, b: MyNat
hab: a < b

succ.zero
a < b
a, b: MyNat
hab: a < b

succ.zero
a < b

Goals accomplished! 🐙
a, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b

succ.succ
succ (succ e) * a < succ (succ e) * b
a, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b

succ.succ
succ (succ e) * a < succ (succ e) * b
a, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b

succ.succ
succ e * a + a < succ (succ e) * b
a, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b

succ.succ
succ e * a + a < succ (succ e) * b
a, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b

succ.succ
succ e * a + a < succ (succ e) * b
a, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b

succ.succ
succ e * a + a < succ e * b + b
a, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b

succ.succ
succ e * a + a < succ e * b + b
a, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b

succ.succ
succ e * a + a < succ e * b + b
a, b: MyNat
hab: a < b
e: MyNat
he: succ e * a < succ e * b

succ e * a + a < succ e * b + a

Goals 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.succ
succ e * b + a < succ e * b + b
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.succ
succ e * b + a < succ e * b + b
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.succ
a + succ e * b < succ e * b + b
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.succ
a + succ e * b < succ e * b + b
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.succ
a + succ e * b < succ e * b + b
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.succ
a + succ e * b < b + succ e * b
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.succ
a + succ e * b < b + succ e * b
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.succ.a
a < b

Goals accomplished! 🐙
theorem
mul_lt_mul_of_pos_right: ∀ (a b c : MyNat), a < b → 0 < c → a * c < b * c
mul_lt_mul_of_pos_right
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
:
MyNat: Type
MyNat
) :
a: MyNat
a
<
b: MyNat
b
0: MyNat
0
<
c: MyNat
c
a: MyNat
a
*
c: MyNat
c
<
b: MyNat
b
*
c: MyNat
c
:=
a, b, c: MyNat

a < b 0 < c a * c < b * c
a, b, c: MyNat
ha: a < b
h0: 0 < c

a * c < b * c
a, b, c: MyNat
ha: a < b
h0: 0 < c

a * c < b * c
a, b, c: MyNat
ha: a < b
h0: 0 < c

c * a < b * c
a, b, c: MyNat
ha: a < b
h0: 0 < c

c * a < b * c
a, b, c: MyNat
ha: a < b
h0: 0 < c

c * a < b * c
a, b, c: MyNat
ha: a < b
h0: 0 < c

c * a < c * b
a, b, c: MyNat
ha: a < b
h0: 0 < c

c * a < c * b
a, b, c: MyNat
ha: a < b
h0: 0 < c

a
a < b
a, b, c: MyNat
ha: a < b
h0: 0 < c
0 < c
a, b, c: MyNat
ha: a < b
h0: 0 < c

a
0 < c

Goals accomplished! 🐙
-- 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. lemma
mul_le_mul: ∀ {a b c d : MyNat}, a ≤ c → b ≤ d → 0 ≤ b → 0 ≤ c → a * b ≤ c * d
mul_le_mul
{
a: MyNat
a
b: MyNat
b
c: MyNat
c
d: MyNat
d
:
MyNat: Type
MyNat
} (
hac: a ≤ c
hac
:
a: MyNat
a
c: MyNat
c
) (
hbd: b ≤ d
hbd
:
b: MyNat
b
d: MyNat
d
) (
nn_b: 0 ≤ b
nn_b
:
0: MyNat
0
b: MyNat
b
) (
nn_c: 0 ≤ c
nn_c
:
0: MyNat
0
c: MyNat
c
) :
a: MyNat
a
*
b: MyNat
b
c: MyNat
c
*
d: MyNat
d
:=
a, b, c, d: MyNat
hac: a c
hbd: b d
nn_b: 0 b
nn_c: 0 c

a * b c * d

Goals accomplished! 🐙
lemma
le_mul: ∀ (a b c d : MyNat), a ≤ b → c ≤ d → a * c ≤ b * d
le_mul
(
a: MyNat
a
b: MyNat
b
c: MyNat
c
d: MyNat
d
:
MyNat: Type
MyNat
) :
a: MyNat
a
b: MyNat
b
c: MyNat
c
d: MyNat
d
a: MyNat
a
*
c: MyNat
c
b: MyNat
b
*
d: MyNat
d
:=
a, b, c, d: MyNat

a b c d a * c b * d
a, b, c, d: MyNat
hab: a b
hcd: c d

a * c b * d
a, b, c, d: MyNat
hab: a b
hcd: c d

a * c b * d
b, c, d: MyNat
hcd: c d
hab: zero b

zero
zero * c b * d
b, c, d: MyNat
hcd: c d
hab: zero b

zero
zero * c b * d
b, c, d: MyNat
hcd: c d
hab: zero b

zero
0 * c b * d
b, c, d: MyNat
hcd: c d
hab: zero b

zero
0 b * d
b, c, d: MyNat
hcd: c d
hab: zero b

zero
0 b * d

Goals accomplished! 🐙
b, c, d: MyNat
hcd: c d
t: MyNat
Ht: t b t * c b * d
hab: succ t b

succ
succ t * c b * d
Warning: unused variable `Ht` [linter.unusedVariables]
b, c, d: MyNat
hcd: c d
t: MyNat
Ht: t b t * c b * d
hab: succ t b

succ
succ t * c b * d
b, c, d: MyNat
hcd: c d
t: MyNat
Ht: t b t * c b * d
hab: succ t b

succ
succ t * c b * d
b, c, d: MyNat
hcd: c d
t: MyNat
Ht: t b t * c b * d
hab: succ t b

succ
succ t * c b * d
b, c, d: MyNat
hcd: c d
t: MyNat
Ht: t b t * c b * d
hab: succ t b

0 c

Goals accomplished! 🐙
b, c, d: MyNat
hcd: c d
t: MyNat
Ht: t b t * c b * d
hab: succ t b
cz: 0 c

succ
succ t * c b * d
b, c, d: MyNat
hcd: c d
t: MyNat
Ht: t b t * c b * d
hab: succ t b
cz: 0 c

0 b

Goals accomplished! 🐙

Goals accomplished! 🐙
lemma
pow_le: ∀ (m n a : MyNat), m ≤ n → m ^ a ≤ n ^ a
pow_le
(
m: MyNat
m
n: MyNat
n
a: MyNat
a
:
MyNat: Type
MyNat
) :
m: MyNat
m
n: MyNat
n
m: MyNat
m
^
a: MyNat
a
n: MyNat
n
^
a: MyNat
a
:=
m, n, a: MyNat

m n m ^ a n ^ a
m, n, a: MyNat
h: m n

m ^ a n ^ a
m, n, a: MyNat
h: m n

m ^ a n ^ a
m, n: MyNat
h: m n

zero
m ^ zero n ^ zero
m, n: MyNat
h: m n

zero
m ^ zero n ^ zero
m, n: MyNat
h: m n

zero
m ^ 0 n ^ 0
m, n: MyNat
h: m n

zero
1 n ^ 0
m, n: MyNat
h: m n

zero
1 1

Goals accomplished! 🐙
m, n: MyNat
h: m n
t: MyNat
Ht: m ^ t n ^ t

succ
m ^ succ t n ^ succ t
m, n: MyNat
h: m n
t: MyNat
Ht: m ^ t n ^ t

succ
m ^ succ t n ^ succ t
m, n: MyNat
h: m n
t: MyNat
Ht: m ^ t n ^ t

succ
m ^ t * m n ^ succ t
m, n: MyNat
h: m n
t: MyNat
Ht: m ^ t n ^ t

succ
m ^ t * m n ^ t * n
m, n: MyNat
h: m n
t: MyNat
Ht: m ^ t n ^ t

succ
m ^ t * m n ^ t * n
m, n: MyNat
h: m n
t: MyNat
Ht: m ^ t n ^ t

succ.a
m ^ t n ^ t
m, n: MyNat
h: m n
t: MyNat
Ht: m ^ t n ^ t
m n
m, n: MyNat
h: m n
t: MyNat
Ht: m ^ t n ^ t

succ.a
m n

Goals accomplished! 🐙
lemma
strong_induction_aux: ∀ (P : MyNat → Prop), (∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m) → ∀ (n c : MyNat), c < n → P c
strong_induction_aux
(
P: MyNat → Prop
P
:
MyNat: Type
MyNat
Prop: Type
Prop
) (
IH: ∀ (m : MyNat), (∀ (b : MyNat), b < m → P b) → P m
IH
:
m: MyNat
m
:
MyNat: Type
MyNat
, (
b: MyNat
b
:
MyNat: Type
MyNat
,
b: MyNat
b
<
m: MyNat
m
P: MyNat → Prop
P
b: MyNat
b
)
P: MyNat → Prop
P
m: MyNat
m
) (
n: MyNat
n
:
MyNat: Type
MyNat
) :
c: MyNat
c
<
n: MyNat
n
,
P: MyNat → Prop
P
c: MyNat
c
:=
P: MyNat Prop
IH: (m : MyNat), ( (b : MyNat), b < m P b) P m
n: MyNat

(c : MyNat), c < n P c
P: MyNat Prop
IH: (m : MyNat), ( (b : MyNat), b < m P b) P m
n: MyNat

(c : MyNat), c < n P c
P: MyNat Prop
IH: (m : MyNat), ( (b : MyNat), b < m P b) P m

zero
(c : MyNat), c < zero P c
P: MyNat Prop
IH: (m : MyNat), ( (b : MyNat), b < m P b) P m
c: MyNat

zero
c < zero P c
P: MyNat Prop
IH: (m : MyNat), ( (b : MyNat), b < m P b) P m
c: MyNat
hc: c < zero

zero
P c
P: MyNat Prop
IH: (m : MyNat), ( (b : MyNat), b < m P b) P m
c: MyNat
hc: c < zero

zero.h
False
P: MyNat Prop
IH: (m : MyNat), ( (b : MyNat), b < m P b) P m
c: MyNat

zero.h
c < zero False

Goals 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 c
P: 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

succ
P e
P: 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

succ
P e
P: 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
P e
P: 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
P e
P: 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
P e
P: 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 b
P: 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
P b
P: 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.a
b < d

Goals accomplished! 🐙
theorem
strong_induction: ∀ (P : MyNat → Prop), (∀ (m : MyNat), (∀ (d : MyNat), d < m → P d) → P m) → ∀ (n : MyNat), P n
strong_induction
(
P: MyNat → Prop
P
:
MyNat: Type
MyNat
Prop: Type
Prop
) (
IH: ∀ (m : MyNat), (∀ (d : MyNat), d < m → P d) → P m
IH
:
m: MyNat
m
:
MyNat: Type
MyNat
, (
d: MyNat
d
:
MyNat: Type
MyNat
,
d: MyNat
d
<
m: MyNat
m
P: MyNat → Prop
P
d: MyNat
d
)
P: MyNat → Prop
P
m: MyNat
m
) :
n: MyNat
n
,
P: MyNat → Prop
P
n: MyNat
n
:=
P: MyNat Prop
IH: (m : MyNat), ( (d : MyNat), d < m P d) P m

(n : MyNat), P n
P: MyNat Prop
IH: (m : MyNat), ( (d : MyNat), d < m P d) P m
n: MyNat

P n
P: MyNat Prop
IH: (m : MyNat), ( (d : MyNat), d < m P d) P m
n: MyNat

a
n < succ n

Goals 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 given hᵢ's, where the hᵢ's are expressions. If an hᵢ is a defined constant f, then the equational lemmas associated with f are used. This provides a convenient way to unfold f.
  • simp [*] simplifies the main goal target using the lemmas tagged with the attribute [simp] and all hypotheses.
  • simp only [h₁, h₂, ..., hₙ] is like simp [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 named idᵢ.
  • simp at h₁ h₂ ... hₙ simplifies the hypotheses h₁ : T₁ ... hₙ : Tₙ. If the target or another hypothesis depends on hᵢ, a new simplified hypothesis hᵢ 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 like fun:
    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 name this is used.
  • The variant have pattern := e is equivalent to match e with | pattern => _, and it is convenient for types that have only one applicable constructor. For example, given h : p ∧ q ∧ r, have ⟨h₁, h₂, h₃⟩ := h produces the hypotheses h₁ : p, h₂ : q, and h₃ : 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, where e is an expression instead of a variable, generalizes e in the goal, and then cases on the resulting variable.
  • Given as : List α, cases as with | nil => tac₁ | cons a as' => tac₂, uses tactic tac₁ for the nil case, and tac₂ for the cons case, and a and as' are used as names for the new variables introduced.
  • cases h : e, where e is a variable or an expression, performs cases on e as above, but also adds a hypothesis h : 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.