> module Lecture12
Inductive Equality
So far
In lecture 13, we learned how to encode propositions as types,
and how to prove them by building proof terms.
We saw examples of properties of natural numbers (`IsEven`)
and of lists (`IsPrefix`).
Today, we will learn how to encode the statement
"term x is equal to term y"
as an inductive type, so called "inductive equality".
> import Data.Vect
> import Data.Nat
> import Syntax.PreorderReasoning
> %default total
Equality as a predicate
(==) 1 2
> is_eq_nat : Nat -> Nat -> Bool
> is_eq_nat 0 0 = True
> is_eq_nat (S k) (S j) = is_eq_nat k j
> is_eq_nat _ _ = False
`==` gives us no information on the structure of its inputs.
"Boolean blindness"
> predecessor : Nat -> Maybe Nat
> predecessor k = case (k == 0) of
> True => Nothing
Now a "redundant" case distinction is necessary,
since Idris cannot know that if `is_eq_nat 0 k` is
False, then `k` must be a successor.
> False => case k of
> 0 => Nothing
> (S k) => Just k
First Attempt: An inductive equality type for Nat
Idea:
* define a type indexed by natural numbers.
* give primitive evidence: "obviously, 0 = 0"
* define inductive "step" to build proofs for more
complicated statements:
"if m = n, then m + 1 = n + 1"
EqualNat is indexed by the pair of natural numbers we want to prove are
equal. As in the last lecture, the type `EqualNat m n` should be inhabited
by a term if and only if m is equal to n.
> data EqualNat : (m , n : Nat) -> Type where
> EqZ : EqualNat 0 0
> EqS : EqualNat m n -> EqualNat (S m) (S n)
-- m = n => S m = S n
We are ready to prove some groundbreaking results:
Proposition: 1 + 1 = 2.
> example : EqualNat (1 + 1) 2
Proof: Construct a proof that 2 = 2, since `1 + 1` reduces to same normal form as `2`.
> example = EqS (EqS EqZ)
This predicate is reflexive:
Proposition: Any term (n : Nat) is equal to itself, according to EqualNat.
for all n : Nat, n = n
> eqNatRefl : {n : Nat} -> EqualNat n n
Proof: Induction on `n`.
> eqNatRefl {n = 0} = EqZ
> eqNatRefl {n = (S k)} = EqS $ eqNatRefl {n = k}
^^^^^^^^^^ curly brackets, since we pattern match on the implicit argument `n`.
The same example as above, but the lemma `eqNatRefl` does the steps for us.
> example' : EqualNat (1 + 1) 2
> example' = eqNatRefl
Improvement: An equality type for *all* inductive types
We can make the type checker do some work for us.
Idea:
* define a proposition `Equal : (x , y : a) -> Type`,
for any type `a`.
* the only way to construct a proof of this proposition is
Refl : {x : a} -> Equal x x
> namespace Equal
This is called `Equal` in Prelude:
data Equal : (x , y : a) -> Type where
Refl : {x : a} -> Equal x x
Curly brackets {...} in a declaration mark an implicit argument. When
using the constructor Refl to build a term, Idris will (most of the
time) figure out on its own what `x` is.
Both terms have no free variables, so reduce to a normal form.
Idris can see that in this case, the normal form is the same for
both terms. Therefore, we can prove equality using Refl.
> example : Equal (2 + 2) (2 * 2)
> example = Refl {x = 4}
In an expression, you can explicitly give (otherwise implicit) arguments
using curly brackets:
{ = }
: The name of the argument as written in the definition.
In this expression, it is called `x`.
: The value of the argument to supply.
We recover one of the constructors of our first attempt (EqS):
> isEqualS : m = n -> (S m) = (S n)
> isEqualS (Refl {x = m}) = Refl {x = S m}
Some propositions are true "by definition", i.e. we can prove them via Refl:
Below, we can prove that `0 + n = 0` this way. Even though `0 + n`
and `n` are different terms, both have the same normal form:
0 + n => plus Z n => n (match a case in definition of `plus`: `plus Z n` is defined as `n`.
n => n (nothing to reduce)
> plusZeroLeftNeutral' : {n : Nat} -> Equal (0 + n) n
> plusZeroLeftNeutral' = Refl
Proposition: n + 0 = n.
We cannot prove this by Refl since there is no case in the definition of `plus`
that matches the pattern `plus n 0`.
Instead, we "look at `n`" (induction on `n`) to find matching definitions:
> plusZeroRightNeutral' : (n : Nat) -> Equal (n + 0) n
> plusZeroRightNeutral' 0 = Refl
> plusZeroRightNeutral' (S k) = let
> ind_hyp : Equal (k + 0) k
> ind_hyp = plusZeroRightNeutral' k
> in
> isEqualS ind_hyp
Properties of inductive equality
* We know: Equal is reflexive (by definition)
* We can deduce:
- Equal is symmetric: x = y => y = x
- Equal is transitive:
x = y => y = z => x = z
* Note: `x = y` is a synonym for `Equal x y`.
This is special syntactic sugar provided by the compiler.
> reflexivity : x = x
> reflexivity = Refl
Proposition: Equality is a symmetric relation. (In Prelude as `sym`)
> symmetry : x = y -> y = x
> symmetry Refl = Refl
Proposition: Equality is a transitive relation. (In Prelude as `trans`)
> transitivity : x' = y -> y = z -> x' = z
> transitivity Refl Refl = Refl
Proposition: Equality is preserved under application of functions.
If x = y, then f(x) = f(y).
This property is called "congruence" (in Prelude as `cong`).
> congruence : (f : a -> b) -> (p : x = y) -> f x = f y
> congruence f Refl = Refl
> isEqualS' : m = n -> (S m) = (S n)
> isEqualS' = cong S
> plusComm : {m, n : Nat} -> m + n = n + m
> plusComm {m=0} {n} = symmetry $ plusZeroRightNeutral n
> plusComm {m=S m} {n} =
> let
> IHm = plusComm {m} {n}
> in Calc $
> |~ S (m + n)
> ~~ S (n + m) ...( cong S IHm )
> ~~ n + S m ...( plus_S n m )
> where
> plus_S : (m, n : Nat) -> S (m + n) = m + (S n)
> plus_S 0 n = Refl
> plus_S (S m) n = let IHm = plus_S m n in cong S IHm
Equality of types
Since all types are also terms in Idris, we can prove equality of types.
For example, `Vect 2 Nat` is a term of type `Type`.
This is useful in situations where we know that two types always reduce to
the same term if all variables are substituted with values, but Idris can't
see that immediately. For example:
Vect (1 + 2) Nat =(reduce)=> Vect 3 Nat
Vect (2 + 1) Nat =(reduce)=> Vect 3 Nat
In that case, we provide Idris with a proof that the terms always reduce to
the same term, and then use congruence to "coerce" a value from one type to
the other.
This is like "casting" terms, but without changing the term, only its type.
Proposition: The type of vectors of length `m + n` is the same as the type
of vectors of length `n + m`.
> vectLenSym : {a : Type} -> {m , n : Nat} -> Vect (m + n) a = Vect (n + m) a
> vectLenSym {a = a} = cong (\len => Vect len a) plusComm
Coercing of values from one type to another:
If we can prove that types `a` and `b` are the same, we can convert a term
of type `a` to a term of type `b`.
> coerce : {0 a , b : Type} -> (ty_eq : a = b) -> a -> b
Proof:
The only way to construct the proof `ty_eq` is if `a` and `b` both reduce to
the same normal form `r`.-
In that case, `x : r` and the goal is of type `r` (inspect it interactively!),
so we can simply reduce `x` unchanged, no type change necessary.
> coerce (Refl {x = r}) = id
Example: Concatenation of vectors, but the result type is coerced using `vectLenSym`.
--> twisted_concat : {a : Type} -> {m , n : Nat} -> Vect m a -> Vect n a -> Vect (n + m) a
--> twisted_concat xs ys = let zs = xs ++ ys in coerce vectLenSym zs
-- This is a common pattern.
-- Idea:
-- (1) Congruence says:
-- if the indices are equal then the indexed types are equal.
-- (2) Coercion says:
-- if the indexed types are equal then there is a function between them.
--
-- Combining these is called "transport":
> public export
> transport : {0 a : Type} -> (0 fiber : a -> Type) ->
> {0 x , y : a} -> (path : x = y) ->
> fiber x -> fiber y
> transport fiber Refl = id
{-
fiber x fiber y
_________ _________
/ \ / \
/ p \ transport fiber path / transport \
| | -------------------> | fiber path |
\ / \ p /
\_________/ \_________/
path
x =============================== y : a
-}
-- Now we can write twisted vector concatenation as a one-liner:
-- twisted Vect concatenation (using transport):
> twisted_concat : {m , n : Nat} -> Vect m a -> Vect n a -> Vect (n + m) a
> twisted_concat xs ys = transport (\len => Vect len a) plusComm (xs ++ ys)
{-
Vect (m + n) a Vect (n + m) a
_________ ________
/ \ transport (Vect _ a) / \
/ \ plus_sym / \
| xs ++ ys | -------------------> | |
\ / \ /
\_________/ \_________/
plus_sym
m + n =============================== n + m : Nat
-}