Logical Foundations

9. IndProp: Inductively Defined Propositions🔗

import LF.Logic
import LF.CustomTactics

9.1. Inductively Defined Propositions🔗

In the Logic chapter, we looked at several ways of writing propositions, including conjunction, disjunction, and existential quantification.

In this chapter, we bring yet another new tool into the mix: inductively defined propositions.

To begin, some examples...

9.1.1. Example: The Collatz Conjecture🔗

The Collatz Conjecture is a famous open problem in number theory.

Its statement is quite simple. First, we define a function collatzStep on numbers as follows:

def div2 (n : Nat) : Nat := match n with | 0 => 0 | 1 => 0 | n' + 2 => div2 n' + 1 def collatzStep (n : Nat) : Nat := bif n.even then div2 n else (3 * n) + 1

Next, we look at what happens when we repeatedly apply collatzStep to some given starting number. For example, collatzStep 12 is 6, and collatzStep 6 is 3, so by repeatedly applying collatzStep we get the sequence 12, 6, 3, 10, 5, 16, 8, 4, 2, 1.

Similarly, if we start with 19, we get the longer sequence 19, 58, 29, 88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1.

Both of these sequences eventually reach 1. The question posed by Collatz was: Is the sequence starting from any positive natural number guaranteed to reach 1 eventually?

To formalize this question in Lean, we might try to define a recursive function that calculates the total number of steps that it takes for such a sequence to reach 1. You can write this definition in a standard programming language, but it is rejected by Lean's termination checker, since the argument to the recursive call, collatzStep n, is not "obviously smaller" than n.

def fail to show termination for reaches1In with errors failed to infer structural recursion: Cannot use parameter n: failed to eliminate recursive application reaches1In (collatzStep n) failed to prove termination, possible solutions: - Use `have`-expressions to prove the remaining goals - Use `termination_by` to specify a different well-founded relation - Use `decreasing_by` to specify your own tactic for discharging this kind of goal n:Nat⊢ collatzStep n < nreaches1In (n : Nat) : Nat := bif n == 1 then 0 else 1 + reaches1In (collatzStep n)
fail to show termination for
  reaches1In
with errors
failed to infer structural recursion:
Cannot use parameter n:
  failed to eliminate recursive application
    reaches1In (collatzStep n)


failed to prove termination, possible solutions:
  - Use `have`-expressions to prove the remaining goals
  - Use `termination_by` to specify a different well-founded relation
  - Use `decreasing_by` to specify your own tactic for discharging this kind of goal
n:Nat⊢ collatzStep n < n

Indeed, this isn't just a pointless limitation: functions in Lean are required to be total, to ensure logical consistency.

Moreover, we can't fix it by devising a more clever termination checker: deciding whether this particular function is total would be equivalent to settling the Collatz conjecture!

Another idea could be to express the concept "eventually reaches 1 in the Collatz sequence" as a recursively defined property of numbers CollatzHoldsFor : Nat → Prop. This is also rejected by the termination checker. In principle, we could convince Lean that div2 n is smaller than n by supplying an appropriate proof. However, we still can't convince it that (3 * n) + 1 is smaller than n!

def fail to show termination for CollatzHoldsFor with errors failed to infer structural recursion: Cannot use parameter n: failed to eliminate recursive application CollatzHoldsFor (div2 n) failed to prove termination, possible solutions: - Use `have`-expressions to prove the remaining goals - Use `termination_by` to specify a different well-founded relation - Use `decreasing_by` to specify your own tactic for discharging this kind of goal n x✝:Nat⊢ div2 n < x✝CollatzHoldsFor (n : Nat) : Prop := match n with | 0 => False | 1 => True | _ => bif n.even then CollatzHoldsFor (div2 n) else CollatzHoldsFor ((3 * n) + 1)
fail to show termination for
  CollatzHoldsFor
with errors
failed to infer structural recursion:
Cannot use parameter n:
  failed to eliminate recursive application
    CollatzHoldsFor (div2 n)


failed to prove termination, possible solutions:
  - Use `have`-expressions to prove the remaining goals
  - Use `termination_by` to specify a different well-founded relation
  - Use `decreasing_by` to specify your own tactic for discharging this kind of goal
n x✝:Nat⊢ div2 n < x✝

Fortunately, there is another way to do it: we can express the concept "reaches 1 eventually in the Collatz sequence" as an inductively defined property of numbers. Intuitively, this property is defined by a set of rules:

              ─────────────────── (one)
               CollatzHoldsFor 1

n.even = true     CollatzHoldsFor (div2 n)
─────────────────────────────────────────── (even)
               CollatzHoldsFor n

n.even = false    CollatzHoldsFor ((3 * n) + 1)
─────────────────────────────────────────────── (odd)
               CollatzHoldsFor n

So there are three ways to prove that a number n eventually reaches 1 in the Collatz sequence:

  • n is 1;

  • n is even and div2 n eventually reaches 1;

  • n is odd and (3 * n) + 1 eventually reaches 1.

We can prove that a number reaches 1 by constructing a (finite) derivation using these rules. For instance, here is the derivation proving that 12 reaches 1 (where we leave out the evenness/oddness premises):

─────────────────────── (one)
  CollatzHoldsFor 1
─────────────────────── (even)
  CollatzHoldsFor 2
─────────────────────── (even)
  CollatzHoldsFor 4
─────────────────────── (even)
  CollatzHoldsFor 8
─────────────────────── (even)
  CollatzHoldsFor 16
─────────────────────── (odd)
  CollatzHoldsFor 5
─────────────────────── (even)
  CollatzHoldsFor 10
─────────────────────── (odd)
  CollatzHoldsFor 3
─────────────────────── (even)
  CollatzHoldsFor 6
─────────────────────── (even)
  CollatzHoldsFor 12

Formally in Lean, the CollatzHoldsFor property is inductively defined:

inductive CollatzHoldsFor : Nat → Prop where | one : CollatzHoldsFor 1 | even {n : Nat} (h₁ : n.even = true) (h₂ : CollatzHoldsFor (div2 n)) : CollatzHoldsFor n | odd {n : Nat} (h₁ : n.even = false) (h₂ : CollatzHoldsFor ((3 * n) + 1)) : CollatzHoldsFor n

What we've done here is to use Lean's inductive definition mechanism to characterize the property "Collatz holds for..." by stating three different ways in which it can hold: (1) Collatz holds for 1, (2) if Collatz holds for div2 n and n is even, then Collatz holds for n, and (3) if Collatz holds for (3 * n) + 1 and n is odd, then Collatz holds for n. This Lean definition directly corresponds to the three rules we wrote informally above.

For particular numbers, we can now prove that the Collatz sequence reaches 1 (we'll look more closely at how it works a bit later in the chapter). Each step applies a rule and discharges the boolean evenness premise by rfl; the recursive premise is then reduced by the kernel from CollatzHoldsFor (div2 12) to CollatzHoldsFor 6, etc.

example : CollatzHoldsFor 12 := ⊢ CollatzHoldsFor 12 ⊢ Nat.even 12 = true⊢ CollatzHoldsFor (div2 12); ⊢ CollatzHoldsFor (div2 12) ⊢ (div2 12).even = true⊢ CollatzHoldsFor (div2 (div2 12)); ⊢ CollatzHoldsFor (div2 (div2 12)) ⊢ (div2 (div2 12)).even = false⊢ CollatzHoldsFor (3 * div2 (div2 12) + 1); ⊢ CollatzHoldsFor (3 * div2 (div2 12) + 1) ⊢ (3 * div2 (div2 12) + 1).even = true⊢ CollatzHoldsFor (div2 (3 * div2 (div2 12) + 1)); ⊢ CollatzHoldsFor (div2 (3 * div2 (div2 12) + 1)) ⊢ (div2 (3 * div2 (div2 12) + 1)).even = false⊢ CollatzHoldsFor (3 * div2 (3 * div2 (div2 12) + 1) + 1); ⊢ CollatzHoldsFor (3 * div2 (3 * div2 (div2 12) + 1) + 1) ⊢ (3 * div2 (3 * div2 (div2 12) + 1) + 1).even = true⊢ CollatzHoldsFor (div2 (3 * div2 (3 * div2 (div2 12) + 1) + 1)); ⊢ CollatzHoldsFor (div2 (3 * div2 (3 * div2 (div2 12) + 1) + 1)) ⊢ (div2 (3 * div2 (3 * div2 (div2 12) + 1) + 1)).even = true⊢ CollatzHoldsFor (div2 (div2 (3 * div2 (3 * div2 (div2 12) + 1) + 1))); ⊢ CollatzHoldsFor (div2 (div2 (3 * div2 (3 * div2 (div2 12) + 1) + 1))) ⊢ (div2 (div2 (3 * div2 (3 * div2 (div2 12) + 1) + 1))).even = true⊢ CollatzHoldsFor (div2 (div2 (div2 (3 * div2 (3 * div2 (div2 12) + 1) + 1)))); ⊢ CollatzHoldsFor (div2 (div2 (div2 (3 * div2 (3 * div2 (div2 12) + 1) + 1)))) ⊢ (div2 (div2 (div2 (3 * div2 (3 * div2 (div2 12) + 1) + 1)))).even = true⊢ CollatzHoldsFor (div2 (div2 (div2 (div2 (3 * div2 (3 * div2 (div2 12) + 1) + 1))))); ⊢ CollatzHoldsFor (div2 (div2 (div2 (div2 (3 * div2 (3 * div2 (div2 12) + 1) + 1))))) All goals completed! 🐙

The Collatz conjecture then states that the sequence beginning from any positive number reaches 1:

def Collatz := ∀ n : Nat, n ≠ 0 → CollatzHoldsFor n

If you succeed in proving this conjecture, you've got a bright future as a number theorist! But don't spend too long on it — it's been open since 1937.

9.1.2. Example: Binary Relation for Comparing Numbers🔗

A binary relation on a set α has Lean type α → α → Prop. This is a family of propositions parameterized by two elements of α — i.e., a proposition about pairs of elements of α.

For example, one familiar binary relation on Nat is Le : Nat → Nat → Prop, the less-than-or-equal-to relation, which can be inductively defined by the following two rules:

  ─────── (le_refl)
  Le n n

  Le n m
──────────── (le_step)
Le n (m + 1)

These rules say that there are two ways to show that a number is less than or equal to another: either observe that they are the same number, or, if the second has the form m + 1, give evidence that the first is less than or equal to m.

namespace LePlayground inductive Le : Nat → Nat → Prop where | refl {n : Nat} : Le n n | step {n m : Nat} (h : Le n m) : Le n (m + 1) scoped infix:50 (priority := high) " ≤ " => Le

This definition is a bit simpler and more elegant than the boolean function Nat.ble we defined in Basics. As usual, Le and Nat.ble are equivalent, and there is an exercise about that later.

example : 3 ≤ 5 := ⊢ 3 ≤ 5 ⊢ 3 ≤ 4; ⊢ 3 ≤ 3; All goals completed! 🐙 end LePlayground

9.1.3. Example: Transitive Closure🔗

Another example: the transitive closure of a relation r is the smallest relation that contains r and that is transitive. This can be defined by the following two rules:

              r x y
         ─────────────── (t_step)
         TransGen r x y

TransGen r x y    TransGen r y z
──────────────────────────────────── (t_trans)
         TransGen r x z

In Lean this looks as follows:

inductive TransGen {α : Type} (r : α → α → Prop) : α → α → Prop where | step {x y : α} (h : r x y) : TransGen r x y | trans {x y z : α} (h₁ : TransGen r x y) (h₂ : TransGen r y z) : TransGen r x z

"Gen" is short for "generated by" — TransGen r means the smallest transitive relation generated by r.

For example, suppose we define a "parent of" relation on a group of people...

inductive Person : Type where | sage | cleo | ridley | moss inductive ParentOf : Person → Person → Prop where | sage_cleo : ParentOf .sage .cleo | sage_ridley : ParentOf .sage .ridley | cleo_moss : ParentOf .cleo .moss

In this example, sage is a parent of both cleo and ridley; and cleo is a parent of moss.

The ParentOf relation is not transitive, but we can define an "ancestor of" relation as its transitive closure:

def AncestorOf : Person → Person → Prop := TransGen ParentOf

Here is a derivation showing that sage is an ancestor of moss:

 ——————————————————— (sage_cleo) ——————————————————— (cleo_moss)
 ParentOf .sage .cleo            ParentOf .cleo .moss
————————————————————— (step)    ————————————————————— (step)
AncestorOf .sage .cleo          AncestorOf .cleo .moss
———————————————————————————————————————————————————— (trans)
                AncestorOf .sage .moss
example : AncestorOf .sage .moss := ⊢ AncestorOf sage moss ⊢ TransGen ParentOf sage ?y⊢ TransGen ParentOf ?y moss⊢ Person ⊢ TransGen ParentOf sage ?y ⊢ ParentOf sage ?y✝; All goals completed! 🐙 ⊢ TransGen ParentOf cleo moss ⊢ ParentOf cleo moss; All goals completed! 🐙

Computing the transitive closure can be undecidable even for a relation r that is decidable (e.g., the CollatzStep relation below, whose closure is CollatzStepMulti), so in general we can't expect to define transitive closure as a boolean function. Fortunately, Lean allows us to define transitive closure as an inductive relation.

The transitive closure of a binary relation cannot, in general, be expressed in first-order logic (see the Logic chapter), since doing so would require quantifying over relations themselves. The logic of Lean is, however, much more powerful — being higher-order, as we saw there — and can easily define such inductive relations.

9.1.4. Example: Reflexive and Transitive Closure🔗

As another example, the reflexive and transitive closure of a relation r is the smallest relation that contains r and that is reflexive and transitive. This can be defined by the following three rules (where we added a reflexivity rule to TransGen):

                   r x y
         ——————————————————————— (step)
           ReflTransGen r x y

         ——————————————————————— (refl)
           ReflTransGen r x x

   ReflTransGen r x y    ReflTransGen r y z
—————————————————————————————————————————————— (trans)
           ReflTransGen r x z
inductive ReflTransGen {α : Type} (r : α → α → Prop) : α → α → Prop where | step {x y : α} (h : r x y) : ReflTransGen r x y | refl {x : α} : ReflTransGen r x x | trans {x y z : α} (h₁ : ReflTransGen r x y) (h₂ : ReflTransGen r y z) : ReflTransGen r x z

For instance, this enables an equivalent definition of the Collatz conjecture. First we define a binary relation corresponding to the "Collatz step function" collatzStep:

def CollatzStep (n m : Nat) : Prop := collatzStep n = m

This Collatz step relation can be used in conjunction with the reflexive and transitive closure operation to define a Collatz multi-step relation, expressing that a number n reaches another number m in zero or more Collatz steps:

def CollatzStepMulti (n m : Nat) : Prop := ReflTransGen CollatzStep n m def Collatz' : Prop := ∀ (n : Nat), n ≠ 0 → CollatzStepMulti n 1

This CollatzStepMulti relation defined in terms of ReflTransGen allows for more interesting derivations than the linear ones of the directly defined CollatzHoldsFor relation:

collatzStep 16 = 8          collatzStep 8 = 4          collatzStep 4 = 2          collatzStep 2 = 1
────────────────── (step)   ───────────────── (step)   ───────────────── (step)   ───────────────── (step)
CollatzStepMulti 16 8       CollatzStepMulti 8 4       CollatzStepMulti 4 2       CollatzStepMulti 2 1
──────────────────────────────────────────── (trans)   ─────────────────────────────────────────── (trans)
              CollatzStepMulti 16 4                                  CollatzStepMulti 4 1
              ───────────────────────────────────────────────────────────────────────── (trans)
                                     CollatzStepMulti 16 1
Exercise★(EqvGen) (Optional, Manually graded)

How would you modify the ReflTransGen definition above to define the reflexive, symmetric, and transitive closure of r?

N.B. The reflexive, symmetric, and transitive closure of a relation is also called its equivalence closure.

-- FILL IN HERE -- FILL IN HERE unexpected end of input

9.1.5. Example: Permutations🔗

The familiar mathematical concept of permutation also has an elegant formulation as an inductive relation. For simplicity, let's focus on permutations of lists with exactly three elements.

We can define such permutations by the following rules:

   ───────────────────────── (swap12)
   Perm3 [a, b, c] [b, a, c]

   ───────────────────────── (swap23)
   Perm3 [a, b, c] [a, c, b]

Perm3 l₁ l₂       Perm3 l₂ l₃
───────────────────────────── (trans)
         Perm3 l₁ l₃

For instance, we can derive Perm3 [1, 2, 3] [3, 2, 1] as follows:

───────────────────────── (swap12)  ─────────────────────── (swap23)
Perm3 [1, 2, 3] [2, 1, 3]            Perm3 [2, 1, 3] [2, 3, 1]
─────────────────────────────────────────────────────────────────(trans)    ───────────────────── (swap12)
Perm3 [1, 2, 3] [2, 3, 1]                                                    Perm3 [2, 3, 1] [3, 2, 1]
───────────────────────────────────────────────────────────────────────────────────────────────────────── (trans)
Perm3 [1, 2, 3] [3, 2, 1]

This definition says:

  • If l₂ can be obtained from l₁ by swapping the first and second elements, then l₂ is a permutation of l₁.

  • If l₂ can be obtained from l₁ by swapping the second and third elements, then l₂ is a permutation of l₁.

  • If l₂ is a permutation of l₁ and l₃ is a permutation of l₂, then l₃ is a permutation of l₁.

In Lean, we can define Perm3 as follows:

inductive Perm3 {α : Type} : List α → List α → Prop where | swap12 {x y z : α} : Perm3 [x, y, z] [y, x, z] | swap23 {x y z : α} : Perm3 [x, y, z] [x, z, y] | trans {l₁ l₂ l₃ : List α} (h₁₂ : Perm3 l₁ l₂) (h₂₃ : Perm3 l₂ l₃) : Perm3 l₁ l₃
Exercise★(perm) (Optional, Manually graded)

According to this definition, is [1, 2, 3] a permutation of itself?

9.1.6. Example: Evenness (yet again)🔗

We've already seen two ways of stating a proposition that a number n is even: We can say

(1) Nat.even n = true (using the recursive boolean function Nat.even), or

(2) ∃ k, n = Nat.double k (using an existential quantifier).

A third possibility, which we'll use as a simple running example in this chapter, is to say that a number is even if we can establish its evenness from the following two rules:

  ────────── (zero)
    Even 0

    Even n
—————————————— (succ_succ)
  Even (n + 2)

Intuitively these rules say that:

  • The number 0 is even.

  • If n is even, then n + 2 is even.

(Defining evenness in this way may seem a bit confusing, since we have already seen two perfectly good ways of doing it. It makes a convenient running example because it is simple and compact, but we will soon return to the more compelling examples above.)

To illustrate how this new definition of evenness works, let's imagine using it to show that 4 is even:

                 ──────── (zero)
                  Even 0
          ─────────────────────── (succ_succ)
          Even (.succ (.succ 0))
────────────────────────────────────────────── (succ_succ)
Even (.succ (.succ (.succ (.succ 0))))

In words, to show that 4 is even, by rule succ_succ, it suffices to show that 2 is even. This, in turn, is again guaranteed by rule succ_succ, as long as we can show that 0 is even. But this last fact follows directly from the zero rule.

We can translate the informal definition of evenness from above into a formal inductive declaration, where each "way that a number can be even" corresponds to a separate constructor:

inductive Even : Nat → Prop where | zero : Even 0 | succ_succ {n : Nat} (h : Even n) : Even (n + 2)

Such definitions are interestingly different from previous uses of inductive for defining inductive datatypes like Nat or List. For one thing, we are defining not a Type (like Nat) or a function yielding a Type (like List), but rather a function from Nat to Prop — that is, a property of numbers. But what is really new is that, because the Nat argument of Even appears to the right of the colon on the first line, it is allowed to take different values in the types of different constructors: 0 in the type of Even.zero and (n + 2) in the type of Even.succ_succ. Accordingly, the type of each constructor must be specified explicitly (after a colon), and each constructor's type must have the form Even n for some natural number n.

In contrast, recall the definition of List:

inductive `List` has already been declaredList (α : Type) : Type where | nil | cons (x : α) (l : List α)

or (equivalently but more explicitly):

inductive `List` has already been declaredList (α : Type) : Type where | nil : List α | cons (x : α) (l : List α) : List α

This definition introduces the α parameter globally, to the left of the colon, forcing the result of List.nil and List.cons to be the same type (i.e., List α). But if we had tried to bring Nat to the left of the colon in defining Even, we would have seen an error:

inductive WrongEven (n : Nat) : Prop where Mismatched inductive type parameter in WrongEven 0 The provided argument 0 is not definitionally equal to the expected parameter n Note: The value of parameter `n` must be fixed throughout the inductive declaration. Consider making this parameter an index if it must vary.| zero : WrongEven 0 | succ_succ (h : WrongEven n) : WrongEven (.succ (.succ n))
Mismatched inductive type parameter in
  WrongEven 0
The provided argument
  0
is not definitionally equal to the expected parameter
  n

Note: The value of parameter `n` must be fixed throughout the inductive declaration. Consider making this parameter an index if it must vary.

In an inductive definition, an argument to the type constructor on the left of the colon is called a "parameter," whereas an argument on the right is called an "index" or "annotation."

For example, in inductive List (α : Type) ..., the α is a parameter, while in inductive Even : Nat → Prop ..., the unnamed Nat argument is an index.

We can think of the inductive definition of Even as defining a Lean property Even : Nat → Prop, together with two "evidence constructors":

Even : Nat → Prop#check (Even) Even.zero : Even 0#check Even.zero Even.succ_succ {n : Nat} (h : Even n) : Even (n + 2)#check Even.succ_succ
Even : Nat → Prop
Even.zero : Even 0
Even.succ_succ {n : Nat} (h : Even n) : Even (n + 2)

These evidence constructors can be thought of as "primitive evidence of evenness," and they can be used later on just like proven theorems. In particular, we can use Lean's apply and exact tactics with the constructor names to obtain evidence for Even of particular numbers...

namespace Even example : Even 4 := ⊢ Even 4 ⊢ Even 2; ⊢ Even 0; All goals completed! 🐙

... or we can use function application syntax to combine several constructors:

example : Even 4 := ⊢ Even 4 All goals completed! 🐙

... or we can also use the constructor tactic we saw earlier to select the appropriate inductive constructor:

example : Even 4 := ⊢ Even 4 ⊢ Even 2; ⊢ Even 0; All goals completed! 🐙

In this way, we can also prove theorems that have hypotheses involving Even.

theorem plus4 (n : Nat) (h : Even n) : Even (4 + n) := n:Nath:Even n⊢ Even (4 + n) n:Nath:Even n⊢ Even (n + 4) All goals completed! 🐙
Exercise★(double)
theorem declaration uses `sorry`double (n : Nat) : Even n.double := n:Nat⊢ Even n.double All goals completed! 🐙
end Even

9.1.7. Constructing Evidence for Permutations🔗

Similarly, we can apply the evidence constructors to obtain evidence of Perm3 [1, 2, 3] [3, 2, 1]:

namespace Perm3 theorem rev : Perm3 [1, 2, 3] [3, 2, 1] := ⊢ Perm3 [1, 2, 3] [3, 2, 1] ⊢ Perm3 [1, 2, 3] [2, 3, 1]⊢ Perm3 [2, 3, 1] [3, 2, 1] ⊢ Perm3 [1, 2, 3] [2, 3, 1] ⊢ Perm3 [1, 2, 3] [2, 1, 3]⊢ Perm3 [2, 1, 3] [2, 3, 1] ⊢ Perm3 [1, 2, 3] [2, 1, 3] All goals completed! 🐙 ⊢ Perm3 [2, 1, 3] [2, 3, 1] All goals completed! 🐙 ⊢ Perm3 [2, 3, 1] [3, 2, 1] All goals completed! 🐙

And again we can equivalently use function application syntax to combine several constructors. (Note that the Lean type checker can infer not only types, but also Nats and Lists, when they are clear from the context.)

theorem rev' : Perm3 [1, 2, 3] [3, 2, 1] := ⊢ Perm3 [1, 2, 3] [3, 2, 1] All goals completed! 🐙

So the informal derivation trees we drew above are not too far from what's happening formally. Formally, we're using the evidence constructors to build evidence trees, similar to the finite trees we built using the constructors of data types such as Nat, List, binary trees, etc.

Exercise★(Perm3)
theorem declaration uses `sorry`ex1 : Perm3 [1, 2, 3] [2, 3, 1] := ⊢ Perm3 [1, 2, 3] [2, 3, 1] All goals completed! 🐙 theorem declaration uses `sorry`refl (α : Type) (a b c : α) : Perm3 [a, b, c] [a, b, c] := α:Typea:αb:αc:α⊢ Perm3 [a, b, c] [a, b, c] All goals completed! 🐙
end Perm3

9.2. Using Evidence in Proofs🔗

Besides constructing evidence that numbers are even, we can also destruct such evidence, reasoning about how it could have been built — i.e., we can introduce and eliminate Even evidence, in the sense of Logic.

Defining Even with an inductive declaration tells Lean not only that the constructors Even.zero and Even.succ_succ are valid ways to build evidence that some number is Even, but also that these two constructors are the only ways to build evidence that numbers are Even.

In other words, if someone gives us evidence e for the proposition Even n, then we know that e must be one of two things:

  • e = Even.zero and n = 0, or

  • e = Even.succ_succ n' e' and n = n' + 2, where e' is evidence for Even n'.

This suggests that it should be possible to analyze a hypothesis of the form Even n much as we do inductively defined data structures; in particular, it should be possible to argue either by case analysis or by induction on such evidence. Let's look at a few examples to see what this means in practice.

9.2.1. Destructing and Inverting Evidence🔗

Suppose we are proving some fact involving a number n, and we are given Even n as a hypothesis. We already know how to perform case analysis on n using cases or induction, generating separate subgoals for the case where n = 0 and the case where n = n' + 1 for some n'. But for some proofs we may instead want to analyze the evidence for Even n directly.

As a tool for such proofs, we can formalize the intuitive characterization that we gave above for evidence of Even n, using cases.

theorem Even.inversion (n : Nat) (h : Even n) : (n = 0) ∨ ∃ n', n = n' + 2 ∧ Even n' := n:Nath:Even n⊢ n = 0 ∨ ∃ n', n = n' + 2 ∧ Even n' cases h with ⊢ 0 = 0 ∨ ∃ n', 0 = n' + 2 ∧ Even n' ⊢ 0 = 0; All goals completed! 🐙 n:Nath:Even n⊢ n + 2 = 0 ∨ ∃ n', n + 2 = n' + 2 ∧ Even n' n:Nath:Even n⊢ ∃ n', n + 2 = n' + 2 ∧ Even n'; All goals completed! 🐙

Facts like this are often called "inversion lemmas" because they allow us to "invert" some given information to reason about all the different ways it could have been derived.

Exercise★(le_inversion)

Let's prove a similar inversion lemma for Le.

namespace LePlayground theorem declaration uses `sorry`le_inversion (n m : Nat) (h : n ≤ m) : (n = m) ∨ (∃ m', m = m' + 1 ∧ n ≤ m') := n:Natm:Nath:n ≤ m⊢ n = m ∨ ∃ m', m = m' + 1 ∧ n ≤ m' All goals completed! 🐙
end LePlayground
Quiz

Which tactics are needed to prove this goal?

∀ (n : Nat), Even n → n = 1 → true = false

(A) cases (B) contradiction (C) Both cases and contradiction (D) These tactics are not sufficient to solve the goal.

Show solution
example (n : Nat) (hEven : Even n) (h : n = 1) : true = false := n:NathEven:Even nh:n = 1⊢ true = false cases hEven with h:0 = 1⊢ true = false All goals completed! 🐙 n✝:Nath✝:Even n✝h:n✝ + 2 = 1⊢ true = false n✝:Nath✝:Even n✝n_eq✝:n✝ + 1 = 0⊢ true = false; All goals completed! 🐙

We can use the inversion lemma that we proved above to help structure proofs:

theorem Even.of_succ_succ (n : Nat) (h : Even (n + 2)) : Even n := n:Nath:Even (n + 2)⊢ Even n n:Nath:n + 2 = 0 ∨ ∃ n', n + 2 = n' + 2 ∧ Even n'⊢ Even n n:Natn':Nath₁:n + 2 = n' + 2h₂:Even n'⊢ Even n n:Natn':Nath₂:Even n'heq:n = n'⊢ Even n n:Nath₂:Even n⊢ Even n All goals completed! 🐙

Note how the inversion lemma produces two subgoals, which correspond to the two ways of proving Even. The first subgoal is a contradiction that is discharged with contradiction. The second subgoal makes use of injections and subst. The subst tactic takes an equation x = t and replaces x by t in the context's hypotheses and in the goal, then removes that equation from the context.

We've defined a handy tactic called inversion that factors out this common pattern, saving us the trouble of explicitly stating and proving an inversion lemma for every inductive definition we make. (The details of how inversion is implemented are beyond the scope of this course. Lean provides metaprogramming facilities that its users can employ to write their own tactics, and these capabilities are powerful enough that just about any algorithmic reasoning steps can be implemented.)

Here, the inversion tactic can detect (1) that the first case, where n = 0, does not apply and (2) that the n' that appears in the Even.succ_succ case must be the same as n.

example (n : Nat) (h : Even (n + 2)) : Even n := n:Nath:Even (n + 2)⊢ Even n n:Nath✝:Even n⊢ Even n; All goals completed! 🐙

The inversion tactic can apply the principle of explosion to "obviously contradictory" hypotheses involving inductively defined properties, something that takes a bit more work using our inversion lemma. Compare:

example : ¬ Even 1 := ⊢ ¬Even 1 h:Even 1⊢ False; h:1 = 0 ∨ ∃ n', 1 = n' + 2 ∧ Even n'⊢ False n':Nath₁:1 = n' + 2h₂:Even n'⊢ False All goals completed! 🐙 example : ¬ Even 1 := ⊢ ¬Even 1 h:Even 1⊢ False; All goals completed! 🐙
Exercise★(inversion_practice)

Prove the following result using inversion. (For extra practice, you can also prove it using the inversion lemma.)

theorem declaration uses `sorry`Even.of_add_four {n : Nat} (h : Even (n + 4)) : Even n := n:Nath:Even (n + 4)⊢ Even n All goals completed! 🐙
Exercise★(even5_nonsense)

Prove the following result using inversion.

theorem declaration uses `sorry`Even.even5_nonsense (h : Even 5) : 2 + 2 = 9 := h:Even 5⊢ 2 + 2 = 9 All goals completed! 🐙

Recall from the Logic chapter that equality (Eq) is itself an inductively defined proposition, so inversion can also be used on equality propositions.

We can use inversion to re-prove some theorems from Tactics.

example (n m o : Nat) (h : [n, m] = [o, o]) : [n] = [m] := n:Natm:Nato:Nath:[n, m] = [o, o]⊢ [n] = [m] n:Nat⊢ [n] = [n]; All goals completed! 🐙 example (n : Nat) (h : n + 1 = 0) : 2 + 2 = 5 := n:Nath:n + 1 = 0⊢ 2 + 2 = 5 All goals completed! 🐙

For the inductively defined propositions we use, inversion behaves much like cases: it performs case analysis on the constructors of the hypothesis's inductive type. However, when the case analysis on an indexed proposition gives unsolvable equations between its indices, cases itself fails, whereas inversion leaves such equations in the context.

For example, cases would immediately fail on h:

example (n : Nat) (h : Even (n * n)) : n * n = 0 ∨ ∃ m, n * n = m + 2 := n:Nath:Even (n * n)⊢ n * n = 0 ∨ ∃ m, n * n = m + 2 All goals completed! 🐙
Dependent elimination failed: Failed to solve equation
  n.mul n = 0

inversion instead leaves the equations in the context, where we can use them directly:

example (n : Nat) (h : Even (n * n)) : n * n = 0 ∨ ∃ m, n * n = m + 2 := n:Nath:Even (n * n)⊢ n * n = 0 ∨ ∃ m, n * n = m + 2 inversion h with | zero => n:Nath:Even (n * n)h✝¹:h ≍ Even.zeroh✝:n.mul n = 0⊢ n * n = 0; All goals completed! 🐙 | succ_succ m' _ _ _ => n:Nath:Even (n * n)m':Nath✝²:Even m'h✝¹:h ≍ ⋯h✝:n.mul n = (m'.add 1).succ⊢ ∃ m, n * n = m + 2; All goals completed! 🐙

Here is a useful way to think about inversion. For an inductively defined hypothesis h, inversion h starts with one case for each constructor, then uses the indices of the type of h to eliminate impossible cases, and simplifies the remaining ones. In the remaining cases, it solves these equations to force some expressions or substitute some variables. If an equation cannot be solved, inversion leaves it in the context and we can use it in the rest of the proof.

Quiz

Which tactics are needed to prove this goal, in addition to apply or exact?

∀ n, Even (2 + n) → Even n

(A) inversion (B) inversion, injections (C) inversion, rw [Nat.add_comm] (D) inversion, rw [Nat.add_comm], injections

Show solution
example (n : Nat) (h : Even (2 + n)) : Even n := n:Nath:Even (2 + n)⊢ Even n n:Nath:Even (n + 2)⊢ Even n inversion h with | _ h => All goals completed! 🐙

9.2.2. Induction on Evidence🔗

The Even.double exercise above allows us to easily show that our new notion of evenness is implied by the two earlier ones. In fact, by Nat.even_bool_prop in the Logic chapter, we already know that those are equivalent to each other. To show that Nat.Even, Even, and Nat.even coincide, we just need the following lemma.

We could try to proceed by cases or induction on n. But since Even is mentioned in a premise, this strategy seems unpromising, because (as we've noted before) the induction hypothesis will talk about n - 1 (which is not even!). Thus, it seems better to first try inversion on the evidence for Even.

example (n : Nat) (h : Even n) : Nat.Even n := n:Nath:Even n⊢ n.Even unsolved goals n':Nath':Even n'⊢ (n' + 2).Eveninversion h with | zero => All goals completed! 🐙 -- The first case can be solved trivially. | succ_succ n' h' => unexpected end of input; expected '{'

Unfortunately, the second case is harder. We need to show ∃ n₀, n' + 2 = double n₀, but the only available assumption is h', which states that Even n' holds. In other words, what we need here is precisely the result we are trying to prove, but applied to the smaller evidence h'.

If this story feels familiar, it is no coincidence: we encountered similar problems in the Induction chapter, when trying to use case analysis to prove results that required induction. And once again the solution is... induction!

The behavior of induction on evidence is the same as its behavior on data: it causes Lean to generate one subgoal for each constructor that could have been used to build that evidence, while providing an induction hypothesis for each recursive occurrence of the property in question.

To prove that a property of n holds for all even numbers (i.e., those for which Even n holds), we can use induction on Even n. This requires us to prove two things, corresponding to the two ways in which Even n could have been constructed. If it was constructed by Even.zero, then n = 0 and the property must hold of 0. If it was constructed by Even.succ_succ, then the evidence of Even n is of the form Even.succ_succ n' h', where n = n' + 2 and h' is evidence for Even n'. In this case, the induction hypothesis says that the property we are trying to prove holds for n'.

Let's try proving that lemma again:

theorem Even.nat_even (n : Nat) (h : Even n) : Nat.Even n := n:Nath:Even n⊢ n.Even induction h with n:Nat⊢ Nat.Even 0 All goals completed! 🐙 -- (`0 = double 0` is closed by `exists`'s final `rfl`) n:Natn✝:Nath':Even n✝ih:n✝.Even⊢ (n✝ + 2).Even n:Natn✝:Nath':Even n✝ih:n✝.Evenk:Nathk:n✝ = k.double⊢ (n✝ + 2).Even n:Natn✝:Nath':Even n✝ih:n✝.Evenk:Nathk:n✝ = k.double⊢ n✝ + 2 = (k + 1).double; All goals completed! 🐙

Here, we can see that Lean produced an ih that corresponds to h, the single recursive occurrence of Even in its own definition. Since h' mentions n', the induction hypothesis talks about n', as opposed to n or some other number.

The equivalence between the second and third definitions of evenness now follows.

theorem Even.iff_nat_even (n : Nat) : Even n ↔ Nat.Even n := n:Nat⊢ Even n ↔ n.Even n:Nat⊢ Even n → n.Evenn:Nat⊢ n.Even → Even n n:Nat⊢ Even n → n.Even n:Nath:Even n⊢ n.Even; All goals completed! 🐙 n:Nat⊢ n.Even → Even n n:Natk:Nathk:n = k.double⊢ Even n; n:Natk:Nathk:n = k.double⊢ Even k.double; All goals completed! 🐙

As we will see in later chapters, induction on evidence is a recurring technique across many areas — in particular for formalizing the semantics of programming languages.

The following exercises provide simpler examples of this technique, to help you familiarize yourself with it.

Exercise★★(Even.add)
theorem declaration uses `sorry`Even.add (n m : Nat) (hn : Even n) (hm : Even m) : Even (n + m) := n:Natm:Nathn:Even nhm:Even m⊢ Even (n + m) All goals completed! 🐙
Exercise★★★(Even.of_add_left) (Advanced)
theorem declaration uses `sorry`Even.of_add_left (n m : Nat) (h : Even (n + m)) (hn : Even n) : Even m := n:Natm:Nath:Even (n + m)hn:Even n⊢ Even m /- Hint: There are two pieces of evidence you could attempt to induct upon here. If one doesn't work, try the other. -/ All goals completed! 🐙
Exercise★★★(add_of_add_left) (Optional)

This exercise can be completed without induction or case analysis. But you will need a clever have and some tedious rewriting. Hint: Is (n + m) + (n + k) even?

theorem declaration uses `sorry`Even.add_of_add_left (n m k : Nat) (hₙₘ : Even (n + m)) (hₙₖ : Even (n + k)) : Even (m + k) := n:Natm:Natk:Nathₙₘ:Even (n + m)hₙₖ:Even (n + k)⊢ Even (m + k) All goals completed! 🐙

Another example of a proposition that can be characterized both recursively and inductively is the List.In predicate we defined in the Logic chapter. As a reminder, the recursive definition we saw looked like this:

def List.In {α : Type} (x : α) (xs : List α) : Prop := match xs with | [] => False | x' :: xs' => x = x' ∨ In x xs'

We can also write this definition inductively like so:

inductive List.In' {α : Type} (x : α) : List α → Prop | head {l : List α} : In' x (x :: l) | tail {y : α} {l : List α} (h : In' x l) : In' x (y :: l)

In fact, this is exactly how Lean defines this proposition, which it calls Membership.mem and which is written x ∈ l. Its negation ¬ x ∈ l is also written as x ∉ l.

A good exercise to test your understanding of induction on evidence is to prove the equivalence of these definitions:

Exercise★★(in_mem)
theorem declaration uses `sorry`List.in_iff_mem {α} (x : α) (l : List α) : List.In x l ↔ x ∈ l := α:Typex:αl:List α⊢ In x l ↔ x ∈ l All goals completed! 🐙

The characterizing lemmas for ∈ are called List.mem_nil_iff and List.mem_cons.

9.2.3. Multiple Induction Hypotheses🔗

Recall the definition of the reflexive, transitive closure of a relation:

inductive ReflTransGen {α : Type} (r : α → α → Prop) : α → α → Prop where | step {x y : α} (h : r x y) : ReflTransGen r x y | refl {x : α} : ReflTransGen r x x | trans {x y z : α} (h₁ : ReflTransGen r x y) (h₂ : ReflTransGen r y z) : ReflTransGen r x z

Let's say that a relation on a type α is diagonal if it refines the identity relation — i.e., if r x y implies x = y.

def Diagonal {α : Type} (r : α → α → Prop) := ∀ {x y}, r x y → x = y

Now consider the following lemma about diagonal relations:

theorem closure_of_diagonal_is_diagonal {α : Type} (r : α → α → Prop) (hDiag : Diagonal r) : Diagonal (ReflTransGen r) := α:Typer:α → α → ProphDiag:Diagonal r⊢ Diagonal (ReflTransGen r) α:Typer:α → α → ProphDiag:Diagonal rx:αy:αh:ReflTransGen r x y⊢ x = y induction h with α:Typer:α → α → ProphDiag:Diagonal rx:αy:αx✝:αy✝:αhr:r x✝ y✝⊢ x✝ = y✝ All goals completed! 🐙 α:Typer:α → α → ProphDiag:Diagonal rx:αy:αx✝:α⊢ x✝ = x✝ All goals completed! 🐙 α:Typer:α → α → ProphDiag:Diagonal rx:αy:αx✝:αy✝:αz✝:αh₁✝:ReflTransGen r x✝ y✝h₂✝:ReflTransGen r y✝ z✝ihxy:x✝ = y✝ihyz:y✝ = z✝⊢ x✝ = z✝ All goals completed! 🐙

Something interesting happens here: there are two induction hypotheses, ihxy and ihyz! If you think about it, it is not that weird: we are in the case trans, which has two recursive components, hxy, relating x to y, and hyz, relating y to z. Hence we may want (and will actually need) an induction hypothesis for hxy and one for hyz — they are called ihxy and ihyz here. In general, Lean will always generate one induction hypothesis per recursive premise of each constructor of the type being inducted over.

Exercise★★★★(Even') (Advanced, Optional)

In general, there may be multiple ways of defining a property inductively. For example, here's a (slightly contrived) alternative definition for Even:

inductive Even' : Nat → Prop where | zero : Even' 0 | two : Even' 2 | add {n m : Nat} (h₁ : Even' n) (h₂ : Even' m) : Even' (n + m)

Prove that this definition is logically equivalent to the old one. To streamline the proof, use the technique (from the Logic chapter) of applying theorems to arguments, and note that the same technique works with constructors of inductively defined propositions.

theorem declaration uses `sorry`Even'.iff_Even n : Even' n ↔ Even n := n:Nat⊢ Even' n ↔ Even n All goals completed! 🐙

We can do similar inductive proofs on the Perm3 relation, which we defined earlier as follows:

inductive Perm3 {α : Type} : List α → List α → Prop where | swap12 {x y z : α} : Perm3 [x, y, z] [y, x, z] | swap23 {x y z : α} : Perm3 [x, y, z] [x, z, y] | trans {l₁ l₂ l₃ : List α} (h₁₂ : Perm3 l₁ l₂) (h₂₃ : Perm3 l₂ l₃) : Perm3 l₁ l₃namespace Perm3 theorem symm {α} (l₁ l₂ : List α) (h : Perm3 l₁ l₂) : Perm3 l₂ l₁ := α:Typel₁:List αl₂:List αh:Perm3 l₁ l₂⊢ Perm3 l₂ l₁ induction h with α:Typel₁:List αl₂:List αx✝:αy✝:αz✝:α⊢ Perm3 [y✝, x✝, z✝] [x✝, y✝, z✝] All goals completed! 🐙 α:Typel₁:List αl₂:List αx✝:αy✝:αz✝:α⊢ Perm3 [x✝, z✝, y✝] [x✝, y✝, z✝] All goals completed! 🐙 α:Typel₁:List αl₂:List αl₁✝:List αl₂✝:List αl₃✝:List αh₁₂✝:Perm3 l₁✝ l₂✝h₂₃✝:Perm3 l₂✝ l₃✝ih₁₂:Perm3 l₂✝ l₁✝ih₂₃:Perm3 l₃✝ l₂✝⊢ Perm3 l₃✝ l₁✝ All goals completed! 🐙

We pause for a moment to point out that some tactics that accept an at clause can target several locations at once, including the goal, written using the ⊢ symbol, by listing them together after at — for instance, both rw and dsimp support this.

example (n m : Nat) (h : n + 0 = m) : n = m + 0 := n:Natm:Nath:n + 0 = m⊢ n = m + 0 n:Natm:Nath:n = m⊢ n = m All goals completed! 🐙

Instead of listing specific targets, you can also write at * to target all the hypotheses and the goal. Here is another example, relevant to the next exercise.

example (hIn : x ∈ [1, 2, 3]) : x ∈ [2, 1, 3] := x:NathIn:x ∈ [1, 2, 3]⊢ x ∈ [2, 1, 3] x:NathIn:x = 1 ∨ x = 2 ∨ x ∈ [3]⊢ x = 2 ∨ x = 1 ∨ x ∈ [3] All goals completed! 🐙
Exercise★★(Perm3_In)

If you find yourself dealing with deeply nested cases in this proof, think back to Logic where you learned about the obtain tactic.

theorem declaration uses `sorry`In {α} (x : α) (l₁ l₂ : List α) (hPerm : Perm3 l₁ l₂) (hIn : x ∈ l₁) : x ∈ l₂ := α:Typex:αl₁:List αl₂:List αhPerm:Perm3 l₁ l₂hIn:x ∈ l₁⊢ x ∈ l₂ All goals completed! 🐙
Exercise★(Perm3_NotIn) (Optional)
theorem declaration uses `sorry`NotIn {α} (x : α) (l₁ l₂ : List α) (hPerm : Perm3 l₁ l₂) (hIn : x ∉ l₁) : x ∉ l₂ := α:Typex:αl₁:List αl₂:List αhPerm:Perm3 l₁ l₂hIn:¬x ∈ l₁⊢ ¬x ∈ l₂ All goals completed! 🐙
Exercise★★(NotPerm3) (Optional)

Proving that something is not a permutation is quite tricky. Some of the lemmas above, like Perm3.In, can be useful for this.

theorem declaration uses `sorry`Not : ¬ Perm3 [1, 2, 3] [1, 2, 4] := ⊢ ¬Perm3 [1, 2, 3] [1, 2, 4] All goals completed! 🐙 end Perm3

9.3. Exercises with Inductive Relations🔗

namespace LePlayground

9.3.1. More Facts about Le🔗

Recall the Le relation from earlier in this chapter. Here are a number of facts about the ≤, <, and ≥ relations, and about Le's relationship to the boolean function Nat.ble, that we are going to need later in the course; the proofs make good practice for the case-analysis and induction techniques from the last few sections.

9.3.1.1. Facts about ≤🔗

Exercise★★★(le_facts)
theorem declaration uses `sorry`le_trans (m n k : Nat) (h₁ : m ≤ n) (h₂ : n ≤ k) : m ≤ k := m:Natn:Natk:Nath₁:m ≤ nh₂:n ≤ k⊢ m ≤ k All goals completed! 🐙 theorem declaration uses `sorry`zero_le (n : Nat) : 0 ≤ n := n:Nat⊢ 0 ≤ n All goals completed! 🐙 theorem declaration uses `sorry`succ_le_succ (n m : Nat) (h : n ≤ m) : n + 1 ≤ m + 1 := n:Natm:Nath:n ≤ m⊢ n + 1 ≤ m + 1 All goals completed! 🐙 theorem declaration uses `sorry`le_of_succ_le_succ (n m : Nat) (h : n + 1 ≤ m + 1) : n ≤ m := n:Natm:Nath:n + 1 ≤ m + 1⊢ n ≤ m All goals completed! 🐙 theorem declaration uses `sorry`le_not_succ_le_self (n : Nat) : ¬ (n + 1 ≤ n) := n:Nat⊢ ¬n + 1 ≤ n All goals completed! 🐙 theorem declaration uses `sorry`le_add_right (n m : Nat) : n ≤ n + m := n:Natm:Nat⊢ n ≤ n + m All goals completed! 🐙
Exercise★★(le_facts1)
theorem declaration uses `sorry`le_and_le_of_add_le (n₁ n₂ m : Nat) (h : n₁ + n₂ ≤ m) : n₁ ≤ m ∧ n₂ ≤ m := n₁:Natn₂:Natm:Nath:n₁ + n₂ ≤ m⊢ n₁ ≤ m ∧ n₂ ≤ m All goals completed! 🐙 theorem declaration uses `sorry`le_or_le_of_add_le_add (n m p q : Nat) (h : n + m ≤ p + q) : n ≤ p ∨ m ≤ q := n:Natm:Natp:Natq:Nath:n + m ≤ p + q⊢ n ≤ p ∨ m ≤ q /- Hint: May be easiest to prove by induction on `n`. -/ All goals completed! 🐙
Exercise★★(plus_le_facts2)
theorem declaration uses `sorry`add_le_add_left (n m p : Nat) (h : n ≤ m) : p + n ≤ p + m := n:Natm:Natp:Nath:n ≤ m⊢ p + n ≤ p + m All goals completed! 🐙 theorem declaration uses `sorry`add_le_add_right (n m p : Nat) (h : n ≤ m) : n + p ≤ m + p := n:Natm:Natp:Nath:n ≤ m⊢ n + p ≤ m + p All goals completed! 🐙 theorem declaration uses `sorry`le_add_right_of_le (n m p : Nat) (h : n ≤ m) : n ≤ m + p := n:Natm:Natp:Nath:n ≤ m⊢ n ≤ m + p All goals completed! 🐙

9.3.1.2. Facts about < and ≥🔗

The "strictly less than" relation n < m can now be defined in terms of Le.

def Lt (n m : Nat) : Prop := Le (n + 1) m scoped infix:50 (priority := high) " < " => Lt

The ≥ relation is defined in terms of ≤, so unfolding its definition with rw lets us move between them.

def Ge (m n : Nat) : Prop := Le n m scoped infix:50 (priority := high) " ≥ " => Ge example (m n : Nat) (h : m ≥ n) : n ≤ m := m:Natn:Nath:m ≥ n⊢ n ≤ m m:Natn:Nath:n ≤ m⊢ n ≤ m All goals completed! 🐙
Exercise★★★(lt_facts) (Optional)
theorem lt_not_lt_zero (n : Nat) : ¬ n < 0 := n:Nat⊢ ¬n < 0 n:Nath:n < 0⊢ False All goals completed! 🐙 theorem declaration uses `sorry`lt_or_ge (n m : Nat) : n < m ∨ n ≥ m := n:Natm:Nat⊢ n < m ∨ n ≥ m All goals completed! 🐙 theorem declaration uses `sorry`le_of_lt (n m : Nat) (h : n < m) : n ≤ m := n:Natm:Nath:n < m⊢ n ≤ m All goals completed! 🐙 theorem declaration uses `sorry`lt_and_lt_of_add_lt (n₁ n₂ m : Nat) (h : n₁ + n₂ < m) : n₁ < m ∧ n₂ < m := n₁:Natn₂:Natm:Nath:n₁ + n₂ < m⊢ n₁ < m ∧ n₂ < m All goals completed! 🐙

9.3.1.3. Relating Le and Nat.ble🔗

Recall that Le and Nat.ble are equivalent (as promised earlier).

Exercise★★★★(ble) (Optional)
theorem declaration uses `sorry`le_of_ble_eq_true (n m : Nat) (h : Nat.ble n m = true) : n ≤ m := n:Natm:Nath:n.ble m = true⊢ n ≤ m All goals completed! 🐙 theorem declaration uses `sorry`ble_eq_true_of_le n m (h : n ≤ m) : Nat.ble n m = true := n:Natm:Nath:n ≤ m⊢ n.ble m = true All goals completed! 🐙

Hint: The next two can easily be proved without using induction.

theorem declaration uses `sorry`ble_eq (n m : Nat) : Nat.ble n m = true ↔ n ≤ m := n:Natm:Nat⊢ n.ble m = true ↔ n ≤ m All goals completed! 🐙 theorem declaration uses `sorry`ble_trans (n m k : Nat) : Nat.ble n m = true → Nat.ble m k = true → Nat.ble n k = true := n:Natm:Natk:Nat⊢ n.ble m = true → m.ble k = true → n.ble k = true All goals completed! 🐙
end LePlayground
Exercise★★★(R_provability) (Manually graded)

We can define three-place relations, four-place relations, etc., in just the same way as binary relations. For example, consider the following three-place relation on numbers:

namespace RProvability inductive R : Nat → Nat → Nat → Prop where | c1 : R 0 0 0 | c2 {m n k : Nat} (h : R m n k) : R (m + 1) n (k + 1) | c3 {m n k : Nat} (h : R m n k) : R m (n + 1) (k + 1) | c4 {m n k : Nat} (h : R (m + 1) (n + 1) (k + 2)) : R m n k | c5 {m n k : Nat} (h : R m n k) : R n m k
  1. Which of the following propositions are provable?

  • R 1 1 2

  • R 2 2 6

  1. If we dropped constructor c5 from the definition of R, would the set of provable propositions change? Briefly (one sentence) explain your answer.

  2. If we dropped constructor c4 from the definition of R, would the set of provable propositions change? Briefly (one sentence) explain your answer.

Exercise★★★(R_fact) (Optional)

The relation R above actually encodes a familiar function. Figure out which function; then state and prove this equivalence in Lean.

def declaration uses `sorry`funR : Nat → Nat → Nat := sorry theorem declaration uses `sorry`funR_iff_R {m n k : Nat} : funR m n = k ↔ R m n k := m:Natn:Natk:Nat⊢ funR m n = k ↔ R m n k All goals completed! 🐙 end RProvability
Exercise★★★★(subsequence) (Advanced)

A list is a subsequence of another list if all of the elements in the first list occur in the same order in the second list, possibly with some extra elements in between. For example,

[1, 2, 3]

is a subsequence of each of the lists

[1, 2, 3]
[1, 1, 1, 2, 2, 3]
[1, 2, 7, 3]
[5, 6, 1, 9, 9, 2, 7, 3, 8]

but it is not a subsequence of any of the lists

[1, 2]
[1, 3]
[5, 6, 2, 1, 7, 3, 8].
  • Define an inductive proposition Subseq on List Nat that captures what it means to be a subsequence. There are a number of correct ways to do this. You should make sure that your definition behaves correctly on all the positive and negative examples above, but you do not need to prove this formally.

  • Prove Subseq.refl that subsequence is reflexive — that is, any list is a subsequence of itself.

  • Prove Subseq.append that for any lists l₁, l₂, and l₃, if l₁ is a subsequence of l₂, then l₁ is also a subsequence of l₂ ++ l₃.

  • (Harder) Prove Subseq.trans that subsequence is transitive — that is, if l₁ is a subsequence of l₂ and l₂ is a subsequence of l₃, then l₁ is a subsequence of l₃.

inductive Subseq : List Nat → List Nat → Prop where -- FILL IN HERE namespace Subseq theorem declaration uses `sorry`refl (l : List Nat) : Subseq l l := l:List Nat⊢ Subseq l l All goals completed! 🐙 theorem declaration uses `sorry`append (l₁ l₂ l₃ : List Nat) (h : Subseq l₁ l₂) : Subseq l₁ (l₂ ++ l₃) := l₁:List Natl₂:List Natl₃:List Nath:Subseq l₁ l₂⊢ Subseq l₁ (l₂ ++ l₃) All goals completed! 🐙 theorem declaration uses `sorry`trans (l₁ l₂ l₃ : List Nat) (h12 : Subseq l₁ l₂) (h23 : Subseq l₂ l₃) : Subseq l₁ l₃ := l₁:List Natl₂:List Natl₃:List Nath12:Subseq l₁ l₂h23:Subseq l₂ l₃⊢ Subseq l₁ l₃ /- Hint: be careful about what you are doing induction on and which other things need to be generalized... -/ All goals completed! 🐙 end Subseq
Exercise★★(R_provability2) (Optional, Manually graded)

Suppose we give Lean the following definition:

namespace RProvability2 inductive R : Nat → List Nat → Prop where | c1 : R 0 [] | c2 {n : Nat} {l : List Nat} (h : R n l) : R (n + 1) (n :: l) | c3 {n : Nat} {l : List Nat} (h : R (n + 1) l) : R n l

Which of the following propositions are provable?

  • R 2 [1, 0]

  • R 1 [1, 2, 1, 0]

  • R 6 [3, 2, 1, 0]

end RProvability2
Exercise★★(total_relation) (Optional)

Define an inductive binary relation TotalRelation that holds between every pair of natural numbers.

inductive TotalRelation : Nat → Nat → Prop where -- FILL IN HERE theorem declaration uses `sorry`total_relation_is_total (n m : Nat) : TotalRelation n m := n:Natm:Nat⊢ TotalRelation n m All goals completed! 🐙
Exercise★★(empty_relation) (Optional)

Define an inductive binary relation EmptyRelation (on numbers) that never holds.

inductive EmptyRelation : Nat → Nat → Prop where -- FILL IN HERE theorem declaration uses `sorry`empty_relation_is_empty (n m : Nat) : ¬ EmptyRelation n m := n:Natm:Nat⊢ ¬EmptyRelation n m All goals completed! 🐙

9.4. Additional Exercises🔗

Exercise★★★(nostutter_defn) (Manually graded)

Formulating inductive definitions of properties is an important skill you'll need in this course. Try to solve this exercise without any help.

We say that a list "stutters" if it repeats the same element consecutively. (This is different from not containing duplicates: the sequence [1, 4, 1] has two occurrences of the element 1 but does not stutter.) The property NoStutter l means that l does not stutter. Formulate an inductive definition for NoStutter.

inductive NoStutter {α : Type} : List α → Prop where -- FILL IN HERE

Make sure each of these tests succeeds, but feel free to change the suggested proof (in comments) if the given one doesn't work for you. Your definition might be different from ours and still be correct, in which case the examples might need a different proof. (You'll notice that the suggested proofs use a number of tactics we haven't talked about, to make them more robust to different possible ways of defining NoStutter. You can probably just uncomment and use them as is, but you can also prove each example with more basic tactics.)

declaration uses `sorry`example : NoStutter [3, 1, 4, 1, 5, 6] := ⊢ NoStutter [3, 1, 4, 1, 5, 6] All goals completed! 🐙 /- Suggested proof — uncomment and adapt: constructor; intro contra; contradiction constructor; intro contra; contradiction constructor; intro contra; contradiction constructor; intro contra; contradiction constructor; intro contra; contradiction constructor -/ declaration uses `sorry`example : NoStutter (@List.nil Nat) := ⊢ NoStutter [] All goals completed! 🐙 /- Suggested proof — uncomment and adapt: constructor -/ declaration uses `sorry`example : NoStutter [5] := ⊢ NoStutter [5] All goals completed! 🐙 /- Suggested proof — uncomment and adapt: constructor -/ declaration uses `sorry`example : ¬ (NoStutter [3, 1, 1, 4]) := ⊢ ¬NoStutter [3, 1, 1, 4] All goals completed! 🐙 /- Suggested proof — uncomment and adapt: intro contra inversion contra with | cons contra => inversion contra with | cons _ h _ => contradiction -/
Exercise★★★★(filter_challenge) (Advanced)

Let's prove that our definition of filter from the Poly chapter matches an abstract specification. Here is the specification, written out informally in English:

A list l is an "in-order merge" of l₁ and l₂ if it contains all the same elements as l₁ and l₂, in the same order as l₁ and l₂, but possibly interleaved. For example,

[1, 4, 6, 2, 3]

is an in-order merge of

[1, 6, 2]

and

[4, 3].

Now, suppose we have a type α, a function test : α → Bool, and a list l of type List α. Suppose further that l is an in-order merge of two lists, l₁ and l₂, such that every item in l₁ satisfies test and no item in l₂ satisfies test. Then filter test l = l₁.

First define what it means for one list to be a merge of two others. Do this with an inductive relation, not a def.

inductive Merge {α : Type} : List α → List α → List α → Prop where -- FILL IN HERE theorem declaration uses `sorry`merge_filter (α : Type) (test : α → Bool) (l l₁ l₂ : List α) (h : Merge l₁ l₂ l) (h₁ : l₁.allb test) (h₂ : l₂.allb (fun x => !test x)) : filter test l = l₁ := α:Typetest:α → Booll:List αl₁:List αl₂:List αh:Merge l₁ l₂ lh₁:List.allb test l₁ = trueh₂:List.allb (fun x => !test x) l₂ = true⊢ filter test l = l₁ All goals completed! 🐙
Exercise★★★★★(filter_challenge_2) (Advanced, Optional)

A different way to characterize the behavior of filter goes like this: Among all subsequences of l with the property that test evaluates to true on all their members, filter test l is the longest. Formalize this claim and prove it.

Exercise★★★★(palindromes) (Optional)

A palindrome is a sequence that reads the same backwards as forwards.

  • Define an inductive proposition Pal on List α that captures what it means to be a palindrome. (Hint: You'll need three cases.)

  • Prove pal_append_reverse, which states that

∀ l, Pal (l ++ l.reverse).
  • Prove pal_reverse, which states that

∀ l, Pal l → l = l.reverse.

For extra credit, try proving the same theorems with an alternate definition with a single constructor of this type:

∀ l, l = l.reverse → Pal l
inductive Pal {α : Type} : List α → Prop where -- FILL IN HERE declaration uses `sorry`example : Pal ([] : List Nat) := ⊢ Pal [] All goals completed! 🐙 /- Suggested proof — uncomment and adapt: constructor -/ declaration uses `sorry`example : Pal [1] := ⊢ Pal [1] All goals completed! 🐙 /- Suggested proof — uncomment and adapt: constructor -/ declaration uses `sorry`example : Pal [1, 2, 1] := ⊢ Pal [1, 2, 1] All goals completed! 🐙 /- Suggested proof — uncomment and adapt: apply Pal.cons_snoc (l := [2]) constructor -/ declaration uses `sorry`example : Pal [1, 2, 3, 2, 1] := ⊢ Pal [1, 2, 3, 2, 1] All goals completed! 🐙 /- Suggested proof — uncomment and adapt: apply Pal.cons_snoc (l := [2, 3, 2]) apply Pal.cons_snoc (l := [3]) constructor -/ theorem declaration uses `sorry`pal_append_reverse (α : Type) (l : List α) : Pal (l ++ l.reverse) := α:Typel:List α⊢ Pal (l ++ l.reverse) All goals completed! 🐙 theorem declaration uses `sorry`pal_reverse {α : Type} {l : List α} (hp : Pal l) : l = l.reverse := α:Typel:List αhp:Pal l⊢ l = l.reverse All goals completed! 🐙
Exercise★★★★(NoDup) (Advanced, Optional, Manually graded)

Use the ∈ property to define a proposition Disjoint l₁ l₂, which should be provable exactly when l₁ and l₂ are lists (with elements of type α) that have no elements in common.

def declaration uses `sorry`Disjoint {α : Type} (l₁ l₂ : List α) : Prop := sorry

Next, use ∈ to define an inductive proposition NoDup l, which should be provable exactly when l is a list (with elements of type α) where every member is different from every other. For example, NoDup ([1, 2, 3, 4] : List Nat) and NoDup ([] : List Bool) should be provable, while NoDup ([1, 2, 1] : List Nat) and NoDup ([true, true] : List Bool) should not be.

inductive NoDup {α : Type} : List α → Prop where -- FILL IN HERE

Finally, state and prove one or more interesting theorems relating Disjoint, NoDup, and ++ (list append).

Exercise★★★★★(pigeonhole_principle) (Advanced, Optional)

The pigeonhole principle states a basic fact about counting: if we distribute more than n items into n pigeonholes, some pigeonhole must contain at least two items. As often happens, this apparently trivial fact about numbers requires nontrivial machinery to prove, but we now have enough...

First prove an easy and useful lemma.

theorem declaration uses `sorry`List.mem_split {α : Type} {x : α} {l : List α} (hin : x ∈ l) : ∃ l₁ l₂, l = l₁ ++ x :: l₂ := α:Typex:αl:List αhin:x ∈ l⊢ ∃ l₁ l₂, l = l₁ ++ x :: l₂ All goals completed! 🐙

Now define a property Repeats such that Repeats l asserts that l contains at least one repeated element.

inductive Repeats {α : Type} : List α → Prop where -- FILL IN HERE

Now, here's a way to formalize the pigeonhole principle. Suppose list l₂ represents a list of pigeonhole labels, and list l₁ represents the labels assigned to a list of items. If there are more items than labels, at least two items must have the same label — i.e., list l₁ must contain repeats.

This proof is much easier if you use the excluded middle to show that ∈ is decidable, i.e., ∀ x l, (x ∈ l) ∨ ¬ (x ∈ l). Remember the by_cases tactic from Logic!

open LePlayground in theorem declaration uses `sorry`pigeonhole_principle {α : Type} {l₁ l₂ : List α} (hin : ∀ x, x ∈ l₁ → x ∈ l₂) (hlen : l₂.length < l₁.length) : Repeats l₁ := α:Typel₁:List αl₂:List αhin:∀ (x : α), x ∈ l₁ → x ∈ l₂hlen:l₂.length < l₁.length⊢ Repeats l₁ All goals completed! 🐙
Source revision: 9cc9a7b, committed 2026-09-28 19:09 UTC