> module Lecture13
Last week we saw how to express equality using an inductive type
family (parameterized type). Elements of this type family are
guarantees that two terms are computationally equal, expressed at the
type level.
This week we will look at how to express that two values are not
equal. This leads us to the idea of decidability, and we will then
look at decidability as inductive type.
> import Data.Nat
> import Data.List
> import Data.Maybe
> import Decidable.Equality
> import Data.Vect
%default total -- for proof validity
Expressing inequality
How do we construct evidence that an element of `Equal x y` cannot exist?
We can use Void
Recall:
data Void : Type where
> twoPlusTwoNotFive : Not $ Equal (2 + 2) 5
A total function returning Void means that its arguments are also impossible to construct at the same time. In other words, if we could construct that argument, we would be able to construct an element of Void (if the function is total).
Not : Type -> Type
Not x = x -> Void
void : Void -> a
Decidable Propositions
A proposition is called *decidable* if we can
either *affirm* it by providing a proof that it is true
or else *refute* it by providing a proof that it is false.
Under the propositions-as-types interpretation of logic this means
either producing an element of the given type,
or else showing that the type is empty by defining
a total function from it to the empty type Void.
> data Dec' : (proposition : Type) -> Type where
-- like the boolean True, but with evidence:
> Yes : (prf : proposition) -> Dec' proposition
-- like the boolean False, but with evidence:
> No : (ref : Not proposition) -> Dec' proposition
> data IsEven : Nat -> Type where
> IsEvenZ : IsEven 0
> IsEvenSS : IsEven n -> IsEven (S (S n))
> IsOdd : Nat -> Type
> IsOdd n = Not (IsEven n)
> two_is_even : Dec (IsEven 2)
> two_is_even = Yes $ IsEvenSS IsEvenZ
> four_is_not_five : Dec (2 + 2 = S 4)
> four_is_not_five = No $ (\x => case x of Refl impossible)
Decidable Predicates
A predicate is *decidable* if we can either
prove or disprove that the predicate holds
of each possible argument.
Under propositions-as-types, this corresponds to
either producing an element of the type determined by the index,
or else proving that type is empty.
A function that decides a predicate
is called a *decision procedure*.
A decision procedure for the IsEven predicate:
> decide_even : (n : Nat) -> Dec (IsEven n)
> decide_even Z = Yes IsEvenZ
> decide_even (S n) = case decide_even n of
> Yes n_even => No $ ?isOddSuccEven' n_even
> No n_odd => Yes $ ?isEvenSuccOdd' n_odd
> where
> isEvenSuccOdd : {n : Nat} -> IsOdd n -> IsEven (S n)
> isEvenSuccOdd {n = 0} zero_odd = void $ zero_odd IsEvenZ
> isEvenSuccOdd {n = 1} one_odd = IsEvenSS IsEvenZ
> isEvenSuccOdd {n = S (S n)} ssn_odd =
> let IH = isEvenSuccOdd {n = n} $ (ssn_odd . IsEvenSS) in
> IsEvenSS IH
Once we have a decision procedure for a predicate
we don't need to write proofs of its instance propositions
by hand anymore:
If the proposition is true then the decision procedure
will find a proof for us.
If the proposition is false then the decision procedure
will tell us why.
> is_four_even : Dec (IsEven 4)
> is_four_even = decide_even 4
> is_three_even = decide_even 3
Decidable Equality
Lemma: the successor function is injective:
> pred_equal : S m = S n -> m = n
> pred_equal Refl = Refl
A decision procedure for Nat equality:
> %hint
> decide_nat_eq : (m , n : Nat) -> Dec (m = n)
> decide_nat_eq Z Z = Yes Refl
> decide_nat_eq Z (S n) = No uninhabited
> decide_nat_eq (S m) Z = No uninhabited
> decide_nat_eq (S m) (S n) = case decide_nat_eq m n of
> Yes m_n_equal => Yes $ cong S m_n_equal
> No m_n_differ => No $ m_n_differ . pred_equal
> does_four_equal_four = decide_nat_eq (2 + 2) (2 * 2)
> does_three_equal_four = decide_nat_eq 3 4
The module Decidable.Equality provides an interface
for types with decidable equality called DecEq
along with many common instances.
> does_true_equal_false = decEq True False
> does_just_three_equal_itself = decEq (Just 3) (Just 3)
> does_nil_equal_cons = decEq (the (List Nat) []) [1,2,3]
Constraint Arguments
An *implicit argument* is written between braces
e.g. {n : Nat} -> ...
Idris tries to infer implicit arguments with a *unification algorithm*
based on syntactic matching and recursion on subterms.
e.g.
Vect n Bool
Vect 3 a
-----------
n = 3 , a = Bool
A *constraint argument* is different kind of implicit argument
(also called an *auto implicit argument*).
Idris tries to infer constraint arguments with a *search algorithm*
that (by default) tries to build a term of a given type
using constructors, recursion, and function literals.
This is the same search algorithm used by the editor integration.
> list_head : (xs : List a) -> {auto nonempty : NonEmpty xs} -> a
> list_head [] = absurd nonempty
> list_head (x :: xs) = x
You can augment the search algorithm by using %hint directives
to specify additional terms that you want Idris to try.
The predecessor of a Nat under the constraint that it is not zero:
> %hint
> nz : {n : Nat} -> Not ((S n) = 0)
> nz = uninhabited
> nat_pred : (n : Nat) -> {auto nonzero : Not (n = 0)} -> Nat
> nat_pred Z = absurd $ nonzero Refl
> nat_pred (S n) = n
A longer example (time permitting):
find the square root of a perfect square
using decidable equality and constraints:
A predicate for perfect squares:
> data IsSquare : Nat -> Type where
> SquareOf : (m : Nat) -> IsSquare (m * m)
e.g.:
> four_is_a_square : IsSquare 4
> four_is_a_square = SquareOf 2
> search_square_root : (n : Nat) -> Maybe (IsSquare n)
> search_square_root n = search_square_root_below n (S n)
> where
-- search for the square root of n strictly below the bound m:
> search_square_root_below : (n , m : Nat) -> Maybe (IsSquare n)
> search_square_root_below n Z = Nothing
> search_square_root_below n (S m) = case decEq n (m * m) of
> Yes Refl => Just $ SquareOf m
> No n_not_mm => search_square_root_below n m
> square_root : (n : Nat) -> {auto is_square : IsJust (search_square_root n)} -> Nat
> square_root n with (search_square_root n)
> square_root n | Nothing = absurd is_square
> square_root (m * m) | Just (SquareOf m) = m