Kind2/THEOREMS.md

1778 lines
40 KiB
Markdown
Raw Normal View History

2021-02-20 02:11:49 +03:00
Theorem Proving
===============
Theorem proving is the main difference between Kind and traditional pure
functional languages like Haskell. Since proving theorems require certain
techniques that aren't common in these languages, this tutorial aims to fill
that gap, supplementing a reader that is familiar with basic functional
programming concepts (like recursion, pattern matching and algebraic datatypes)
with the required knowledge to start proving theorems right now.
Before starting, make sure to install Kind via Haskell or JavaScript
(example: `npm i -g kind-lang`), clone this repository
(`https://github.com/uwu-tech/kind`) and `cd` into the `kind/base`
2021-03-06 00:18:15 +03:00
directory (you **must** be there for now). You'll be editing the `Main.kind` file
only. Open it in your favorite editor and type `kind Main.kind` to type-check it.
2021-02-20 02:11:49 +03:00
The Equal type
--------------
To start proving theorems, first you must be aware of the `Equal` type:
```
type Equal <A: Type> (a: A) ~ (b: A) {
2021-02-20 04:22:31 +03:00
refl ~ (b = a)
2021-02-20 02:11:49 +03:00
}
```
2021-02-20 04:22:31 +03:00
If that looks alien to you, don't worry for now, it will become clear. The
`Equal` type has only one constructor, `refl`, which has the following type:
2021-02-20 02:11:49 +03:00
```
Equal.refl : (A: Type) -> (x: A) -> Equal(A,x,x)
````
That means `refl` receives a type (`A`), and element `x` of type `A`, and
returns `Equal(A,x,x)`. As an example, the program below:
```
two_is_two: Equal(Nat, 2, 2)
Equal.refl(Nat, 2)
```
Is a proof that `2 == 2`. Not that scary, right? Since `Equal` and `refl` are
common, there is a shortcut to write them. The program above is equivalent to:
```
two_is_two: 2 == 2
refl
```
2021-03-06 00:18:15 +03:00
Make sure to replace the contents of `Main.kind` by the snipped above, and type
`kind Main.kind` to test it. You should see:
2021-02-20 02:11:49 +03:00
```
two_is_two: 2 == 2
All terms check.
```
Meaning that we indeed proved that 2 and 2 are equal. We can use `Equal.refl` to
prove an equality when both sides are equal, or when they reduce to the same
term. That means this also works:
```
two_is_two: (1 + 1) == 2
refl
```
Because `(1 + 1)` is a concrete value that computes to `2`. Now, of course,
proving that two concrete values are equal isn't that interesting. The real fun
starts when you start mixing letters; aka, variables; in these equations.
Theorem proving, in its most essential form, is all about techniques to
manipulate these equations until you get both sides to be identical. That's what
we're going to do through all this tutorial. Ready? Let's go!
---
Our first theorem: double negation
----------------------------------
We'll start proving a simple theorem: that, for any Boolean b, `not(not(b)) ==
b`. In other words, we'll prove that negating a boolean twice results on the
original boolean. To start this proof, we give the theorem a name, a type, and
a body (a goal):
```
double_negation(b: Bool): Bool.not(Bool.not(b)) == b
?a
```
2021-03-06 00:18:15 +03:00
We now run `kind Main.kind`. That will display:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Bool.not(Bool.not(b)) == b
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
```
That just tells us that we must fill the body of `double_negation` with a proof
that `not(not(b)) == b`; nothing new here. Let's try using `Equal.refl`:
```
double_negation(b: Bool): Bool.not(Bool.not(b)) == b
Equal.refl(Bool, b)
```
This will show a type error:
```
Type mismatch.
- Expected: Bool.not(Bool.not(b)) == b
- Detected: b == b
With context:
- b: Bool
```
That's because `Equal.refl(Bool, b)` proves that `b == b`, but we want a proof
that `Bool.not(Bool.not(b)) == b`. Yes, we, humans, know that
`Bool.not(Bool.not(b))` should always reduce to `b`, but the machine, Kind,
2021-03-06 00:18:15 +03:00
doesn't know that. Why? Let's see the definition of `Bool.not` (from `Bool.kind`):
2021-02-20 02:11:49 +03:00
```
Bool.not(b: Bool): Bool
case b {
true: false
false: true
}
```
When Kind sees `Bool.not(Bool.not(b))`, it reduces it to:
```
Bool.not(case b { true: false, false: true })
```
But now the inner case is **stuck**, trying to pattern-match on `b`. It can't
choose any branch, because it doesn't know what `b` is. It isn't `true`, it
isn't `false`. It is just a variable. That is the only reason Kind is not
able to tell that `Bool.not(Bool.not(b))` and `b` are the same. But we can help
it with a **case analysis**. That is, we can **pattern-match** on `b`:
```
double_negation(b: Bool): not(not(b)) == b
case b {
true: ?a
false: ?b
}
```
2021-03-06 00:18:15 +03:00
Check it with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Bool.not(Bool.not(b)) == b
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
Goal ?b:
With type: Bool.not(Bool.not(b)) == b
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
```
Now, instead of one goal, we have two goals. But Kind is still demanding a
proof that `Bool.not(Bool.not(b)) == b` in both, so, that wasn't very helpful.
But consider the following: on the `true` case, we know that `b` is `true`,
because we just pattern-matched it. Similarly, on the `false` case, we know that
`b` is `false`. Wouldn't it be great if Kind noticed that, and relaxed its
demands by specializing `b` to its concrete value on each branch? We can ask
Kind to do just that by adding a `!` after the case expression:
```
double_negation(b: Bool): Bool.not(Bool.not(b)) == b
case b {
true: ?a
false: ?b
}!
```
Here, `!` stands for "please, be less demanding and specialize b to its concrete
2021-03-06 00:18:15 +03:00
values on each case". By running `kind Main.kind`, we now see:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Bool.not(Bool.not(Bool.true)) == Bool.true
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
Goal ?b:
With type: Bool.not(Bool.not(Bool.false)) == Bool.false
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
```
Kind now demands a proof that `not(not(true)) == true` on the first case,
and a proof that `not(not(false)) == false` on the second one. That's great,
because the variable `b` is gone and the `case b` inside `not` is now "unstuck"
and ready to compute. To see that this is true, Kind allows us to inspect
and reduce a goal by writing `-` after it:
```
double_negation(b: Bool): Bool.not(Bool.not(b)) == b
case b {
true: ?a-
false: ?b-
}!
```
2021-03-06 00:18:15 +03:00
Running `kind Main.kind`, we see:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Bool.not-10(Bool.not-22(Bool.true-30)) == Bool.true-3
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
Goal ?b:
With type: Bool.not-10(Bool.not-22(Bool.false-30)) == Bool.false-3
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
```
Notice how a `-N` was added after each reducible expression. That's a label. We
can ask Kind to reduce any of these by writting that label after the goal.
Let's reduce the inner `not` (the one labelled `-22`) on each branch:
```
double_negation(b: Bool): Bool.not(Bool.not(b)) == b
case b {
true: ?a-22
false: ?b-22
}!
```
2021-03-06 00:18:15 +03:00
Running `kind Main.kind`, we see:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Bool.not(Bool.false) == Bool.true
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
Goal ?b:
With type: Bool.not(Bool.true) == Bool.false
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
```
Notice how the inner `Bool.not` was reduced, going from, on the `?a` branch,
`not(not(true)) == true` to `not(false) == true`. We can repeat the step above
to reduce the last `not`:
```
double_negation(b: Bool): Bool.not(Bool.not(b)) == b
case b {
true: ?a-22-10
false: ?b-22-10
}!
```
2021-03-06 00:18:15 +03:00
Running `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Bool.true == Bool.true
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
Goal ?b:
With type: Bool.false == Bool.false
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
```
That means Kind wants us to prove that `true == true` and `false == false`
on each branch respectively. That is easy: since both sides are equal, we can
just use `refl`:
```
double_negation(b: Bool): Bool.not(Bool.not(b)) == b
case b {
true: refl
false: refl
}!
```
2021-03-06 00:18:15 +03:00
By running `kind Main.kind`, we see:
2021-02-20 02:11:49 +03:00
```
double_negation: (b:Bool) Bool.not(Bool.not(b)) == b
All terms check.
```
That means our first proof is complete, and `not(not(b)) == b` is indeed true.
Good job!
Note that you could have written `refl` on its full form (for example,
`Equal.refl(Bool, Bool.true)`) instead of just `refl`. Note also that this last
step of reducing the goal with `-` is optional. If you knew that `not(not(true))
== true` would reduce to `true == true`, you could have just written `refl`
directly. Learning to use `-` can be very handy when you have goals too complex
to reduce in your head, so keep it in your toolbelt!
An easy theorem: `and(true, b) == b`
------------------------------------
Let's now prove that `and(true, b) == b`. That's true because, if `b` is `true`,
then the equation becomes `and(true,true) == true`, which reduces to `true ==
true`, which holds. And, if `b` is `false`, the equation becomes
`and(true,false) == false`, which reduces to `false == false`, which holds.
Let's prove it. As always, we start by writting a name, a type and a body:
```
and_true_b(b: Bool): Bool.and(true, b) == b
?a
```
2021-03-06 00:18:15 +03:00
By running `kind main.kind`, we see:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Bool.and(Bool.true,b) == b
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
```
Notice that there is a variable `b` on the left side of the equation. That means
that we must pattern-match, because it is stuck... or is it? Let's recall the
2021-03-06 00:18:15 +03:00
definition of `Bool.and` (from `Bool.kind`):
2021-02-20 02:11:49 +03:00
```
Bool.and(a: Bool, b: Bool): Bool
case a {
true: b,
false: false,
}
```
Notice that this function only pattern-matches on `a`, not on `b`. That means
`Bool.and(true,b)` is **not** stuck, and immediately reduces to `b`. We can
convince ourselves of that by reducing `Bool.and`. First, add a `-` to the
goal:
```
and_true_b(b: Bool): Bool.and(true, b) == b
?a-
```
2021-03-06 00:18:15 +03:00
Run it with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Bool.and-18(Bool.true-26,b) == b
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
```
Apply the `Bool.and` labelled with `18`:
```
and_true_b(b: Bool): Bool.and(true, b) == b
?a-18
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: b == b
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
```
Since both sides are equal, we can complete this proof with `refl`:
```
and_true_b(b: Bool): Bool.and(true, b) == b
refl
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
and_true_b: (b:Bool) Bool.and(Bool.true,b) == b
All terms check.
```
And done! Notice how we didn't need to use `case` on this proof. The lesson is:
having variables inside an expression doesn't mean it is stuck, it depends on
the definition of the used functions. As a rule of thumb, when proving a
theorem, don't just pattern-match every variable you see on the goal. Always use
`-` to test if your goal can be simplified as is. Only if it can't you should
pattern-match to specialize a variable to its concrete values.
A hard theorem: `and(b, true) == b`
-----------------------------------
Let's now prove `and(b, true) == b`:
```
and_b_true(b: Bool): Bool.and(b, true) == b
?a
```
That is almost identical to the last theorem we proved (`and(true, b) == b`), so
it should be just `refl` too, right? Wrong! Let's see what happens if we try to
reduce `Bool.and`. To do so, first, write `-` after the goal:
```
and_b_true(b: Bool): Bool.and(b, true) == b
?a-
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Bool.and-18(b,Bool.true-14) == b
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
```
Reduce the `Bool.and` labelled with `18`:
```
and_b_true(b: Bool): Bool.and(b, true) == b
?a-18
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: b((a) Bool,Bool.true,Bool.false) == b
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
```
What? Unlike the previous theorem, reducing `Bool.and` didn't turn our type into
`b == b` immediately. Instead, it became `b(...) == b`. That's because
`Bool.and` is defined to pattern-match on the first argument, which, now, is the
variable `b`, instead of the concrete boolean `true`. That causes the left side
to be stuck trying to apply `b` to these things. To get it unstuck, we must use
a case on `b`:
```
and_b_true(b: Bool): Bool.and(b, true) == b
case b {
true: ?a
false: ?b
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Bool.and(Bool.true,Bool.true) == Bool.true
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
Goal ?b:
With type: Bool.and(Bool.false,Bool.true) == Bool.false
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
```
Now we could have used `-` to reduce `Bool.and` on both goals to convince
ourselves that they reduce to `true == true` and `false == false` respectively.
Since I already know that, I'll just write `refl` on both cases:
```
and_b_true(b: Bool): Bool.and(b, true) == b
case b {
true: refl
false: refl
}!
```
That completes our third proof. The lesson is: again, use `-` to see how your
goal reduces, use case to specialize stuck variables, and use `refl` when you
manage to get two sides equal. With this simple procedure and enough patience,
you can prove any equality theorem.
A hard theorem made easy: `b == and(b,true)`
--------------------------------------------
Let's now prove that `b == and(b,true)`:
```
right_and_b_true(b: Bool): b == Bool.and(b, true)
?a
```
Since we have a variable `b`, and not a concrete value, as the first argument of
`and(b,true)`, and, since `and` pattern-matches on its first argument, then we
must need a pattern-match on this proof. Right? Kinda. Indeed we could do that:
```
right_and_b_true(b: Bool): b == Bool.and(b, true)
case b {
true: refl
false: refl
}!
```
And that would complete our proof. But in this case, since we have just proven
`and(b,true) == b`, which is the same thing just flipped,that means we can reuse
the former proof to prove `right_and_b_true`:
```
and_b_true(b: Bool): Bool.and(b, true) == b
case b {
true: refl
false: refl
}!
right_and_b_true(b: Bool): b == Bool.and(b, true)
mirror(and_b_true(b))
```
Here, `mirror : (A: Type) -> (a: A) -> (b: B) -> Equal(A,a,b) -> Equal(A,b,a)`
is a standard function that flips the two arguments of an equality. That is, it
turns `a == b` into `b == a`. The lesson here is that not always we will prove
equalities with `refl` directly. If we did that, every proof would be huge!
Instead, we can use former proofs inside later proofs, simplifying their code.
This is a functional programming language, after all. Modularism, function reuse
and clean code apply to proofs too!
Exercises
---------
As an exercise, prove the following theorems:
```
and_false_b(b: Bool): Bool.and(false, b) == false
?a
and_b_false(b: Bool): Bool.and(b, false) == false
?a
or_true_b(b: Bool): Bool.or(true, b) == true
?a
or_b_true(b: Bool): Bool.or(b, true) == true
?a
or_false_b(b: Bool): Bool.or(false, b) == b
?a
or_b_false(b: Bool): Bool.or(b, false) == b
?a
eql_b_b(b: Bool): Bool.eql(b, b) == true
?a
demorgan_0(a: Bool, b: Bool): Bool.not(Bool.and(a,b)) == Bool.or(Bool.not(a),Bool.not(b))
?a
demorgan_1(a: Bool, b: Bool): Bool.not(Bool.or(a,b)) == Bool.and(Bool.not(a),Bool.not(b))
?a
```
---
An inductive proof: `half(double(n)) == n`
------------------------------------------
Let's now prove the simplest "inductive" theorem on natural numbers: that the
half of the double of a natural number is itself. As usual, we begin by writting
a name, a type and a body:
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
?a
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Nat.half(Nat.double(n)) == n
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
```
Here, we could try using `-` to reduce `Nat.double` and check if it simplifies.
Let's recall the definition of `Nat.double` and `Nat.half`:
```
Nat.double(n: Nat): Nat
case n {
zero: Nat.zero,
succ: Nat.succ(Nat.succ(Nat.double(n.pred))),
}
Nat.half(n: Nat): Nat
case n {
zero: Nat.zero,
succ: case n.pred {
zero: Nat.zero,
succ: Nat.succ(Nat.half(n.pred.pred))
}
}
```
Since `half` uses `case` on `n`, it gets stuck, so we use `case` on `n` in our
proof to unstuck it:
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
case n {
zero: ?a
succ: ?b
}!
```
2021-03-06 00:18:15 +03:00
Run with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Nat.half(Nat.double(0)) == 0
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
Goal ?b:
With type: Nat.half(Nat.double(Nat.succ(n.pred))) == Nat.succ(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
```
On the `zero` case, we must prove that `half(double(0)) == 0`. This reduces to
`0 == 0` (you can use `-` to verify that), so we just write `refl`:
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
case n {
zero: refl
succ: ?b
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?b:
With type: Nat.half(Nat.double(Nat.succ(n.pred))) == Nat.succ(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
```
Since there is just one goal left, that means the first case is correct. Now,
for the `succ` case, remember that the successor of a number can be written as
`1 + pred`, where `pred` is the predecessor of `n`. As such, Kind demands
us to prove that `half(double(succ(pred))) == succ(pred)`. Let's try to simplify
the left side of that equation. Write a `-` after the goal:
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
case n {
zero: refl
succ: ?b-
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?b:
With type: Nat.half-10(Nat.double-22(Nat.succ-46(n.pred))) == Nat.succ-5(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
```
Reduce the call to `Nat.double` (label `22`):
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
case n {
zero: refl
succ: ?b-22-
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?b:
With type: Nat.half-10(Nat.succ-22(Nat.succ-46(Nat.double-94(n.pred)))) == Nat.succ-5(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
```
Reduce the call to `Nat.half` (label `10`):
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
case n {
zero: refl
succ: ?b-22-10
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?b:
With type: Nat.succ(Nat.half(Nat.double(n.pred))) == Nat.succ(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
```
It seems like not much has changed, but we managed to get the `Nat.succ` out of
the `half(double(...))` expression. But now `double` is stuck on a variable
again: `n.pred`. Remember `.` is just part of the name, `n.pred` is the
predecessor of `n`. We could pattern-match on `n.pred` to unstuck it:
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
case n {
zero: refl
succ: case n.pred {
zero: ?a
succ: ?b
}!
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Nat.half(Nat.double(1)) == 1
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
Goal ?b:
With type: Nat.half(Nat.double(Nat.succ(Nat.succ(n.pred.pred)))) == Nat.succ(Nat.succ(n.pred.pred))
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
- n.pred.pred: Nat
```
The first goal now demands a proof that `half(double(1)) == 1`. Both sides are
now concrete and variable-free, so it reduces to `1 == 1`, which can be
completed with `refl`:
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
case n {
zero: refl
succ: case n.pred {
zero: refl
succ: ?b
}!
}!
```
Great, we now proved the theorem for `n = 0` and `n = 1`! But now the second
goal now became:
```
Goal ?b:
With type: Nat.half(Nat.double(Nat.succ(Nat.succ(n.pred.pred)))) == Nat.succ(Nat.succ(n.pred.pred))
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
- n.pred.pred: Nat
```
This is similar to the situation we had before. With enough patience, we could
"lift" the `Nat.succ` out of the `half(double(...))` expression:
```
Goal ?b:
With type: Nat.succ(Nat.succ(Nat.half(Nat.double(n.pred.pred)))) == Nat.succ(Nat.succ(n.pred.pred))
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
- n.pred.pred: Nat
```
But now `Nat.double` is succ on `n.pred.pred`. We can use `case` on it too:
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
case n {
zero: refl
succ: case n.pred {
zero: refl
succ: case n.pred.pred {
zero: ?a
succ: ?b
}!
}!
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Nat.half(Nat.double(2)) == 2
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
- n.pred.pred: Nat
Goal ?b:
With type: Nat.half(Nat.double(Nat.succ(Nat.succ(Nat.succ(n.pred.pred.pred))))) == Nat.succ(Nat.succ(Nat.succ(n.pred.pred.pred)))
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
- n.pred.pred: Nat
- n.pred.pred.pred: Nat
```
Once again, the first goal is now concrete and we can fill with `refl`:
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
case n {
zero: refl
succ: case n.pred {
zero: refl
succ: case n.pred.pred {
zero: refl
succ: ?b
}!
}!
}!
```
That means we have proven our theorem for `n=0`, `n=1` and `n=2`. But the last
case is even larger. We could go on forever and forever, but we would never
finish this proof because there are infinitely many natural numbers. So, we must
take a step back where the loop began. Remember we had the following proof:
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
case n {
zero: refl
succ: ?b-22-10
}!
```
2021-03-06 00:18:15 +03:00
And the following goal (check with `kind Main.kind`):
2021-02-20 02:11:49 +03:00
```
Goal ?b:
With type: Nat.succ(Nat.half(Nat.double(n.pred))) == Nat.succ(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
```
Since we were stuck on `n.pred`, we used `case` on it. But this resulted in an
endless chain of `case`s. Let's instead try something different: instead of
pattern-matching on `n.pred`, let's use `half_double_theorem` **recursively** on
it:
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
case n {
zero: refl
succ:
let ind = half_double_theorem(n.pred)
?b-22-10
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?b:
With type: Nat.succ(Nat.half(Nat.double(n.pred))) == Nat.succ(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
2021-08-11 20:44:47 +03:00
- ind: Nat.half(Nat.double(n.pred)) == n.pred
2021-02-20 02:11:49 +03:00
```
Our goal didn't change, but now we have a new variable, `ind` in our context. We
gained it "for free" by calling `half_double_theorem` recursively on its
predecessor. Since `half_double_theorem` returns `half(double(n)) == n` for any
`n`, and since we applied it to `n.pred`, then `ind` has type
`half(double(n.pred)) == n.pred`. That's the **inductive hypothesis**. Take a
moment to realize that this type is almost the same as our goal: the only
difference is that the goal has an extra `Nat.succ` on each side. We can apply a
function to both sides of an equality using `apply(f,e)`.
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
case n {
zero: refl
succ:
let ind = half_double_theorem(n.pred)
let app = apply(Nat.succ, ind)
?b-22-10
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?b:
With type: Nat.succ(Nat.half(Nat.double(n.pred))) == Nat.succ(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
- ind: Nat.half(Nat.double(n.pred)) == n.pred
- app: Nat.succ(Nat.half(Nat.double(n.pred))) == Nat.succ(n.pred)
```
The type of `app` is exactly the type of our goal, so we can complete the proof
with it:
```
half_double_theorem(n: Nat): Nat.half(Nat.double(n)) == n
case n {
zero: refl
succ:
let ind = half_double_theorem(n.pred)
let app = apply(Nat.succ, ind)
app
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
half_double_theorem: (n:Nat) Nat.half(Nat.double(n)) == n
All terms check.
```
Great! This one was really hard, wasn't it? That's because the concept of
induction is hard to grasp, yet, induction is nothing but a fancy name for
2021-05-04 08:05:07 +03:00
recursion when used inside a proof. The lesson here is that there
2021-02-20 02:11:49 +03:00
is a new way to "unstuck" a variable in a goal. Instead of pattern-matching on
it (with case), we can apply the function recursively to it. This will give us
"for free" a term of type `a == b` that is closer to the goal. Then, by
manipulating that equation (with `mirror` and `apply`, for example), we can
prove our goal.
A non-inductive proof: `(0 + n) == n`
--------------------------------------
Let's now prove that `0 + n == n` for any `n`. We start as usual:
```
add_0_n(n: Nat): (0 + n) == n
?a
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Nat.add(0,n) == n
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
```
Kind asks us to prove `Nat.add(0,n) == n`. It would be tempting to
pattern-match on `n`. But let's recall the definition of `Nat.add` (from
2021-03-06 00:18:15 +03:00
`Nat.kind`):
2021-02-20 02:11:49 +03:00
```
Nat.add(n: Nat, m: Nat): Nat
case n {
zero: m,
succ: Nat.succ(Nat.add(n.pred, m)),
}
```
Since it pattern-matches on the first argument, and since the first argument of
`0 + n` is concrete, then the goal reduces immediately to `n == n`. You can
verify that by using the `-` feature. As such, this proof is trivial:
```
add_0_n(n: Nat): (0 + n) == n
refl
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
add_0_n: (n:Nat) Nat.add(0,n) == n
All terms check.
```
Done!
An inductive proof: (n + 0) == n
--------------------------------
Let's now prove `(n + 0) == n`:
```
add_n_0(n: Nat): (n + 0) == n
?a
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Nat.add(n,0) == n
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
```
In this case, the first argument of `Nat.add` is a variable, `n`, instead of a
concrete value like `0`. It will get stuck. As usual, we unstuck it by using
`case`:
```
add_n_0(n: Nat): (n + 0) == n
case n {
zero: ?a
succ: ?b
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Nat.add(0,0) == 0
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
Goal ?b:
With type: Nat.add(Nat.succ(n.pred),0) == Nat.succ(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
```
We can reduce `Nat.add` on the goal `?a` using `-`:
```
add_n_0(n: Nat): (n + 0) == n
case n {
zero: ?a-18
succ: ?b
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: 0 == 0
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
Goal ?b:
With type: Nat.add(Nat.succ(n.pred),0) == Nat.succ(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
```
Since both sides of the `?a` goal are equal, we complete it with `refl`:
```
add_n_0(n: Nat): (n + 0) == n
case n {
zero: refl
succ: ?b
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?b:
With type: Nat.add(Nat.succ(n.pred),0) == Nat.succ(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
```
On the left side of the goal `?b`, we have `add(succ(n.pred),0)`. From the
definition of `Nat.add`, we know that this is equivalent to
`succ(add(n.pred,0))`, so we use `-` to reduce `add` and simplify the goal:
```
add_n_0(n: Nat): (n + 0) == n
case n {
zero: refl
succ: ?b-18
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?b:
With type: Nat.succ(Nat.add(n.pred,0)) == Nat.succ(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
```
Now `add` has a variable, `n.pred`, on its first argument, so it is stuck.
Instead of using `case` on `n.pred` - which will lead to an infinite cascade of
cases - we use the inductive hypothesis. That is, we apply `add_n_0` recursively
to `n.pred`:
```
add_n_0(n: Nat): (n + 0) == n
case n {
zero: refl
succ:
let ind = add_n_0(n.pred)
?b-18
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?b:
With type: Nat.succ(Nat.add(n.pred,0)) == Nat.succ(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
- ind: Nat.add(n.pred,0) == n.pred
```
Thanks to the inductive hypothesis, we gained `ind: add(n.pred,0) == n.pred` for
free. The type of `ind` is almost equal to our goal, except that it is missing a
`Nat.succ` on both sides. We add it using `apply`:
```
add_n_0(n: Nat): (n + 0) == n
case n {
zero: refl
succ:
let ind = add_n_0(n.pred)
let app = apply(Nat.succ, ind)
?b-18
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?b:
With type: Nat.succ(Nat.add(n.pred,0)) == Nat.succ(n.pred)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- n: Nat
- n.pred: Nat
- ind: Nat.add(n.pred,0) == n.pred
- app: Nat.succ(Nat.add(n.pred,0)) == Nat.succ(n.pred)
```
The type of `app` is now the same as our goal, so we complete the proof with it:
```
add_n_0(n: Nat): (n + 0) == n
case n {
zero: refl
succ:
let ind = add_n_0(n.pred)
let app = apply(Nat.succ, ind)
app
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
add_n_0: (n:Nat) Nat.add(n,0) == n
All terms check.
```
Done! As you can see, proofs about natural numbers often follow this same
structure. On the zero case, write `refl`. On the succ case, use the inductive
hypothesis on `n.pred` and manipulate `ind` to reach the `goal`. Of course, it
varies depending on what you're trying to prove, but often that's the way.
Exercises
---------
As an exercise, prove the following theorems:
```
lte_0_n(n: Nat): Nat.lte(0, n) == true
?a
gte_n_0(n: Nat): Nat.gte(n, 0) == true
?b
eql_n_n(n: Nat): Nat.eql(n, n) == true
?c
is_even_double_n(n: Nat): Nat.is_even(Nat.double(n)) == true
?d
gte_succ_n_0(n: Nat): Nat.gte(Nat.succ(n), 0) == true
?e
gte_succ_n_n(n: Nat): Nat.gte(Nat.succ(n), n) == true
?f
```
Here, `Nat.lte(a,b)` stands for `a <= b` and `Nat.gte(a,b)` stands for `a >= b`
2021-03-06 00:18:15 +03:00
for natural numbers `a`, `b`. Check their definitions on `Nat.kind`.
2021-02-20 02:11:49 +03:00
Proving an inequality: `1 != 0`
===============================
In Kind, to prove that a theorem is true, we construct an element of the
corresponding type. But how do we prove that something is **not** true? To do
it, we must show that constructing an element of that type is impossible. The
easiest way to do it is by contradiction: we first assume that the theorem holds
and, based on that assumption, construct an element of the Empty type. Since the
Empty type can't be constructed, then our theorem can't be true. But what is the
Empty type?
Remember the Boolean type has 2 elements:
```
type Bool {
true
false
}
```
Similarly, the Unit type has 1 element:
```
type Unit {
unit
}
```
And a Suit type has 4 elements:
```
type Suit {
hearts
spades
clubs
diamonds
}
```
If we can create a type with 1, 2 and 4 elements, can we create a type with 0
elements? Sure, that's the Empty type:
```
type Empty {
}
```
But what is the point of a type with zero elements? It is useless, right? Wrong!
In fact it is one of the most useful types, because of how `case` expressions
work, and the *princible of explosion*. In order for a `case` expression to be
considered well-typed in Kind, you must provide a proof that it holds on
each case. For example, recall the double negation theorem:
```
double_negation(b: Bool): Bool.not(Bool.not(b)) == b
case b {
true: refl
false: refl
}!
```
Here, Kind demanded us a proof of `not(not(true)) == true` on the true
case, and a proof of `not(not(false)) == false` on the false case. Since we
provided a proof for each case of `b`, the `case` expression returned a proof of
`not(not(b)) == b`. That is, from a proof of each specific case (`true` /
`false`) we generated a proof of the general case (a variable `b : Bool`). But
what happens if we use `case` on a variable `e` of type `Empty`?
```
xablau(e: Empty): 1 == 0
case e {
}!
```
Here, Kind will demand us to prove that `1 == 0` holds for each possible
value of `e`. But `e` has no case, so, technically, we satisfied Kind's
demand by doing nothing. Kind then returns a proof that `1 == 0` holds for
every possible value of `e`. So, did we just prove that `1 == 0`? No! Because
`xablau` is a function that demands an element of the type `Empty`. But the
`Empty` type has no elements, so we can never call `xablau`!
That's the principle of explosion: from an absurd, we can derive any other
absurd. That's why `Empty` is so useful. If we can derive `Empty` from
a theorem, then that theorem is definitely false. For example, let's prove
that `1 == 0` is not true. We write a function:
```
one_neq_zero(e: 1 == 0): Empty
?a
```
Which stands for "if 1 == 0, then we can construct an element of the Empty
type". Which can be read as "1 == 0 is a false theorem".
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Empty
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- e: 1 == 0
```
That means that, to prove that "1 == 0" is indeed false, our goal is to construct
an element of the `Empty` type, based on the assumption that `1 == 0` (named `e`).
To do it, you must understand two other things. First, that we can have
expressions on types. For example:
```
two: if true then Nat else String
2
```
This is a perfectly valid Kind definition. Notice how we used an expression
(`if`) inside the type of `two`. This is completely allowed. If you replaced `if
true` by `if false`, the program above wouldn't type-check anymore (try it!). We
can also write inline type annotations in Kind with `::`, so, for example:
```
let two = 2 :: if true then Nat else String
```
Is also valid.
Second, we can rewrite `a` by `b` inside a type if we have a proof that `a ==
b`. For example, suppose we had the following context:
```
eq: n == 2
xs: Vector(Nat, n)
```
That is, we have a vector `xs` of `n` natural numbers, and a proof `eq` that `n
== 2`. We can use `rewrite` and `eq` to cast `xs` to type `Vector(Nat, 2)`:
```
let ys = xs :: rewrite x in Vector(Nat, x) with eq
```
This would give us the following context:
```
eq: n == 2
xs: Vector(Nat, n)
ys: Vector(Nat, 2)
```
Take a moment to make sure the above makes sense to you. If you want to, you may
2021-03-06 00:18:15 +03:00
check the definition of `rewrite` on the `Equal.kind` file (although that may be a
2021-02-20 02:11:49 +03:00
little bit too indimidating for now!).
With these two insights in mind (that we have expressions inside types, and that
we can rewrite equal values inside types), we can prove that `one_neq_zero` by
using a very clever trick. Ready? Here it is:
```
one_neq_zero(e: 1 == 0): Empty
let a = 42 :: if Nat.eql(1,1) then Nat else Empty
let b = a :: rewrite x in (if Nat.eql(1,x) then Nat else Empty) with e
b
```
Now take a breath, and let me explain what is going on.
First, we created a local variable, `a`, of type `if Nat.eql(1,1) then Nat else
Empty`. Since `Nat.eql(1,1)` is true, then `a` must have type `Nat`. So, we just
write `42`, but any number would work.
Second, we used the fact that `1 == 0` to replace the second `1` on the type of
`42`, from `1` to `0`. That means that `b` will have type `if Nat.eql(1,0) then
Nat else Empty`. But `Nat.eql(1,0)` is `false`, so, `b` has type `Empty`. Which
is what we were trying to construct!
In other words, we managed to create an element of type `Empty` by assuming that
`1 == 0`, plus a clever use of `rewrite` to "trick" Kind. Since the `Empty`
type has no elements, that means that, by contradiction, `1` is different of
`0`. In other words, whenever we have a proof in the following shape:
```
a_neq_b(e: a == b): Empty
proof_by_contradiction
```
Then we know, for sure, that `a` and `b` are different. Since this is so common,
you can also write it as `a != b`:
```
a_neq_b: a != b
(e) proof_by_contradiction
```
Here, `a != b` is a synonym of `Not(a == b)`, which is a synonym of `(e: a == b)
-> Empty`, which is why we write a lambda `(e)` on the body of `a_neq_b`.
Uff! That was a lot of information. Let's prove another inequality, but this
time being a little bit more straightforward.
Proving another inequality: `3 != 2`
-----------------------------------
Let's now prove that `3 != 2`. As usual, start with the name, type and body:
```
three_neq_two: 3 != 2
?a
```
2021-03-06 00:18:15 +03:00
Check it with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Not(3 == 2)
```
As expected, Kind just tells us we must prove `Not(3 == 2)`, which is the
same as `(e: 3 == 2) -> Empty`. Since that is a function, we create a lambda:
```
three_neq_two: 3 != 2
(e) ?a
```
2021-03-06 00:18:15 +03:00
Check it with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Empty
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- e: 3 == 2
```
Now Kind is demanding that we derive an element of type `Empty` given a
value of type `3 == 2`. We could proceed by using the rewrite trick above, but,
let's be honest, it is confusing and doing it every time would be annoying.
2021-03-06 00:18:15 +03:00
Instead, we will use the following function, exported by `Bool.kind`:
2021-02-20 02:11:49 +03:00
```
Bool.true_neq_false: true != false
```
Which, remember, is equivalent to:
```
Bool.true_neq_false: (e: true == false) -> Empty
```
So, here is the plan: since we have `e : 3 == 2`, and a function that, given
`true == false` returns `Empty` (which is our goal), then we'll just manipulate
`e` to transform its type from `3 == 2` to `true == false`, and then we'll send
it to `Bool.true_neq_false`, resulting in `Empty` (our goal). Let's use
`Nat.pred` to subtract 1 from both sides:
```
three_neq_two: 3 != 2
(e)
let e0 = apply(Nat.pred, e)
?a
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Empty
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- e: 3 == 2
- e0: 2 == 1
```
Now we have `e0: 2 == 1`. Let's do it again:
```
three_neq_two: 3 != 2
(e)
let e0 = apply(Nat.pred, e)
let e1 = apply(Nat.pred, e0)
?a
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Empty
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- e: 3 == 2
- e0: 2 == 1
- e1: 1 == 0
```
Now we have `e1 : 1 == 0`. Let's now apply `Nat.is_zero` to both sides:
```
three_neq_two: 3 != 2
(e)
let e0 = apply(Nat.pred, e)
let e1 = apply(Nat.pred, e0)
let e2 = apply(Nat.is_zero, e1)
?a
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Empty
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- e: 3 == 2
- e0: 2 == 1
- e1: 1 == 0
- e2: false == true
```
Let's now flip `e2` using `mirror`:
```
three_neq_two: 3 != 2
(e)
let e0 = apply(Nat.pred, e)
let e1 = apply(Nat.pred, e0)
let e2 = apply(Nat.is_zero, e1)
let e3 = mirror(e2)
?a
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Empty
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- e: 3 == 2
- e0: 2 == 1
- e1: 1 == 0
- e2: false == true
- e3: true == false
```
Perfect, we now have `e3: true == false` in our context. Since
`Bool.true_neq_false` receives `true == false` and returns `Empty` (our goal),
we just apply it to `e3`:
```
three_neq_two: 3 != 2
(e)
let e0 = apply(Nat.pred, e)
let e1 = apply(Nat.pred, e0)
let e2 = apply(Nat.is_zero, e1)
let e3 = mirror(e2)
Bool.true_neq_false(e3)
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
three_neq_two: Not(3 == 2)
All terms check.
```
Perfect!
Proving an inequality with variables: not(b) != b
-------------------------------------------------
Let's now prove that `not(b) != b`. We start as usual:
```
2021-05-04 08:05:07 +03:00
not_a_neq_a(b: Bool): Bool.not(b) != b
2021-02-20 02:11:49 +03:00
?a
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Not(Bool.not(b) == b)
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- a: Bool
```
As expected, Kind demands that we prove `Not(not(b) == b)`, which is the
same as `(not(b) == b) -> Empty`. Since that is a function, we add a lambda:
```
not_a_neq_a(b: Bool): Bool.not(b) != b
(e) ?a
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Empty
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- a: Bool
- e: Bool.not(b) == b
```
Now we must construct `Empty` from `e: not(b) == b`. Let's pattern-match on `b`:
```
not_a_neq_a(b: Bool): Bool.not(b) != b
(e)
case b {
true: ?a
false: ?b
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Empty
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
- e: Bool.not(b) == b
Goal ?b:
With type: Empty
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
- e: Bool.not(b) == b
```
This isn't helpful: we now have two branches with identical goals and contexts.
But notice that `b` is true on the first branch, and false on the second branch,
yet `e: Bool.not(b) == b` wasn't "updated" to reflect that fact. Can we update
it? Sure: just move the lambda to inside the case expression. This will allow
Kind to rewrite the type of `e` with the concrete values of `b` on each
branch:
```
not_a_neq_a(b: Bool): Bool.not(b) != b
case b {
true: (e) ?a
false: (e) ?b
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
Goal ?a:
With type: Empty
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
- e: Bool.not(Bool.true) == Bool.true
Goal ?b:
With type: Empty
2021-04-06 05:56:52 +03:00
With context:
2021-02-20 02:11:49 +03:00
- b: Bool
- e: Bool.not(Bool.false) == Bool.false
```
Great! now we have `e: false == true` in one branch, and `e: true == false` on
the other, and we must prove `Empty`. Now, remember that we can use
`Bool.true_neq_false` and `Bool.false_neq_true` to create `Empty` from `true ==
false` and `false == true` respectively. As such, we just apply these functions
to `e`:
```
not_a_neq_a(b: Bool): Bool.not(b) != b
case b {
true: (e) Bool.false_neq_true(e)
false: (e) Bool.true_neq_false(e)
}!
```
2021-03-06 00:18:15 +03:00
Check with `kind Main.kind`:
2021-02-20 02:11:49 +03:00
```
not_a_neq_a: (b:Bool) Not(Bool.not(b) == b)
All terms check.
```
Perfect! Notice that here we did something new: we used case of to specialize
the value of a variable on the context, not the goal, to reflect the concrete
value of `b` on each branch. Another way to do it is by using the `with`
notation:
```
not_a_neq_a(b: Bool): Bool.not(b) != b
(e)
case b
2021-08-11 20:44:47 +03:00
with e : (Bool.not(b) == b) {
2021-02-20 02:11:49 +03:00
true: Bool.false_neq_true(e)
false: Bool.true_neq_false(e)
}!
```
This "moves" `e: Bool.not(b) == b` to inside the pattern-match, specializing `b`
on its type. It has the same effect of moving the lambda inside. It may be more
or less verbose depending of what you're trying to prove.
Exercises
---------
Prove the following inequalities:
```
or_true_a_neq_false(a: Bool): Bool.or(true, a) != false
?a
or_a_true_neq_false(a: Bool): Bool.or(a, true) != false
?a
and_false_a_neq_true(a: Bool): Bool.and(false, a) != true
?a
and_a_false_neq_true(a: Bool): Bool.and(a, false) != true
?a
```
Boss fight
----------
These theorems are harder than the rest, but can be proven from the techniques
presented here:
```
add_succ_n_m(n: Nat, m: Nat): Nat.add(Nat.succ(n), m) == Nat.succ(Nat.add(n, m))
?a
add_n_succ_m(n: Nat, m: Nat): Nat.add(n, Nat.succ(m)) == Nat.succ(Nat.add(n, m))
?a
add_a_b(a: Nat, b: Nat): (a + b) == (b + a)
?a
succ_n_neq_n(n: Nat): Nat.succ(n) != n
?a
```
Hints:
- To prove `add_a_b`, use `add_0_n` and `add_n_succ_m`.
- To prove `succ_n_neq_n`, use `case` to specialize `e` (like on `not_a_neq_a`)
and the inductive hypothesis.
2021-08-12 19:58:12 +03:00
If you got here congratulations! For more challenges read our [problem list for newcomers](https://github.com/uwu-tech/Kind/discussions/259)