{--
-- ITI0212 Lecture week 14, 2021.04.26
--
-- Decidability and Automation
--
--}
module Lecture14
import Data.Nat
import Data.List
import Data.Maybe
import Decidable.Equality
import Lecture11
import Lecture12
import Lecture13
import Lab13
%default total
{- Decidable Propositions -}
-- A proposition is *decidable* if we can
-- either affirm it by providing a proof of it
-- or else refute it by providing a proof of its negation.
-- Under the propositions-as-types interpretation 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 Decide : (proposition : Type) -> Type where
-- like the boolean True, but with a proof:
Affirm : (proof : proposition) -> Decide proposition
-- like the boolean False, but with a proof:
Refute : (refutation : Not proposition) -> Decide proposition
two_is_even : Decide (Even 2)
two_is_even = Affirm $ SS_even Z_even
four_is_not_five : Decide (2 + 2 = S 4)
four_is_not_five = Refute $ \ p => case p of Refl impossible -- absurd
-- The Idris standard library has a version of
-- our Decide type constructor called Dec,
-- with constructors called Yes and No.
{- 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 to be empty.
-- A function that decides a predicate
-- is called a *decision procedure*.
-- a decision procedure for the Even predicate:
decide_even : (n : Nat) -> Dec (Even n)
decide_even Z = Yes Z_even
decide_even (S n) = case decide_even n of
Yes n_even => No $ succ_even_odd n_even
No n_odd => Yes $ succ_odd_even n_odd
-- Once we have a decision procedure for a predicate
-- we don't need to write proofs of its instances by hand anymore:
-- if the proposition is true then the decision procedure
-- will find a proof for us.
is_four_even : Dec (Even 4)
is_four_even = decide_even 4
is_three_even : Dec (Even 3)
is_three_even = decide_even 3
-- a decision procedure for Nat equality:
decide_nat_eq : (m , n : Nat) -> Dec (m = n)
decide_nat_eq Z Z = Yes Refl
decide_nat_eq Z (S n) = No absurd
decide_nat_eq (S m) Z = No absurd
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 : ?
does_four_equal_four = decide_nat_eq (2 + 2) (2 * 2)
does_three_equal_four : ?
does_three_equal_four = decide_nat_eq 3 4
{-
- Idris provides an interface for types with decidable equality:
DecEq : Type -> Constraint
decEq : {a : Type} -> DecEq a => (x , y : a) -> Dec (x = y)
- Many common types from the standard library are declare
instances of this interface in the module Decidable.Equality
-}
does_true_equal_true : Dec (True = True)
does_true_equal_true = decEq _ _
does_true_equal_false : Dec (True = False)
does_true_equal_false = decEq _ _
does_just_three_equal_itself : Dec (Just 3 = Just 3)
does_just_three_equal_itself = decEq _ _
does_nil_equal_cons : Dec (the (List Nat) [] = [1,2,3])
does_nil_equal_cons = decEq _ _
-- let's write a decision procedure for list equality:
decide_list_eq : DecEq a => (xs , ys : List a) -> Dec (xs = ys)
decide_list_eq [] [] = Yes Refl
decide_list_eq [] (y :: ys) = No absurd
decide_list_eq (x :: xs) [] = No absurd
decide_list_eq (x :: xs) (y :: ys) =
case decEq x y of
No x_y_differ => No $ heads_differ x_y_differ
Yes x_y_equal =>
case decide_list_eq xs ys of
No xs_ys_differ => No $ tails_differ xs_ys_differ
Yes Refl => Yes $ cong ( :: ys) x_y_equal
{-- Auto Implicit Arguments --}
-- Implicit arguments are convenient because sometimes Idris can
-- figure out what some argument must be based on other arguments,
-- or the know type of the whole term.
-- The algorithm used to infer implicit arguments is called *unification*.
-- It it is based on textual matching and recursion on subterms.
-- In contrast, an *auto implicit argument* represents a constraint
-- that is intended to be satisfied by Idris's term search algorithm.
-- By default this uses constructors and recursion to search for
-- a term of a given type.
-- However you may specify additional terms of your program for it to try
-- by using the %hint directive.
-- natural number subtraction with a search against underflow:
subtract : (n , m : Nat) -> {auto ordered : LTE m n} -> Nat
subtract n Z = n
subtract Z (S m) = absurd ordered
subtract (S n) (S m) = subtract n m {ordered = lte_pred ordered}
-- Getting the term search strategy to work can be rather tricky.
-- You can add %hint directives for the cases that you want to automate,
-- but you can't control the order in which they are tried,
-- or see why a search failed.
-- used in (1)
public export
implementation Uninhabited (Not (x = x)) where
uninhabited x_not_x = x_not_x Refl
-- used in (2)
%hint
public export
uninhabited_is_empty : (empty : Uninhabited a) => Not a
uninhabited_is_empty x = uninhabited x
nat_pred : (n : Nat) -> {auto nonzero : Not (n = 0)} -> Nat
nat_pred Z = absurd nonzero -- (1) or void $ nonzero Refl
nat_pred (S n) = n -- (2)
-- A longer example using decidable predicates and auto implicit arguments:
-- (Omitted from Lecture for lack of time)
-- a predicate for perfect squares:
data Square : Nat -> Type where
SquareOf : (m : Nat) -> Square (m * m)
-- eg:
four_is_a_square : Square 4
four_is_a_square = SquareOf 2
search_square_root : (n : Nat) -> Maybe (Square n)
search_square_root n = search_square_root_below n (S n)
where
search_square_root_below : (n , m : Nat) -> Maybe (Square 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 contra => 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