module plfa.part1.Decidable where

We have a choice as to how to represent relations: as an inductive data type of evidence that the relation holds, or as a function that computes whether the relation holds. Here we explore the relation between these choices. We first explore the familiar notion of booleans, but later discover that these are best avoided in favour of a new notion of decidable.

Imports

import Relation.Binary.PropositionalEquality as Eq
open Eq using (_≡_; refl)
open Eq.≡-Reasoning
open import Data.Nat using (; zero; suc)
open import Data.Product using (_×_) renaming (_,_ to ⟨_,_⟩)
open import Data.Sum using (_⊎_; inj₁; inj₂)
open import Relation.Nullary using (¬_)
open import Relation.Nullary.Negation using ()
  renaming (contradiction to ¬¬-intro)
open import Data.Unit using (; tt)
open import Data.Empty using (; ⊥-elim)
open import plfa.part1.Relations using (_<_; z<s; s<s)
open import plfa.part1.Isomorphism using (_⇔_)

Evidence vs Computation

Recall that Chapter Relations defined comparison as an inductive datatype, which provides evidence that one number is less than or equal to another:

infix 4 _≤_

data _≤_ :     Set where

  z≤n :  {n : }
      --------
     zero  n

  s≤s :  {m n : }
     m  n
      -------------
     suc m  suc n

For example, we can provide evidence that 2 ≤ 4, and show there is no possible evidence that 4 ≤ 2:

2≤4 : 2  4
2≤4 = s≤s (s≤s z≤n)

¬4≤2 : ¬ (4  2)
¬4≤2 (s≤s (s≤s ()))

The occurrence of () attests to the fact that there is no possible evidence for 2 ≤ 0, which z≤n cannot match (because 2 is not zero) and s≤s cannot match (because 0 cannot match suc n).

An alternative, which may seem more familiar, is to define a type of booleans:

data Bool : Set where
  true  : Bool
  false : Bool

Given booleans, we can define a function of two numbers that computes to true if the comparison holds and to false otherwise:

infix 4 _≤ᵇ_

_≤ᵇ_ :     Bool
zero ≤ᵇ n       =  true
suc m ≤ᵇ zero   =  false
suc m ≤ᵇ suc n  =  m ≤ᵇ n

The first and last clauses of this definition resemble the two constructors of the corresponding inductive datatype, while the middle clause arises because there is no possible evidence that suc m ≤ zero for any m. For example, we can compute that 2 ≤ᵇ 4 holds, and we can compute that 4 ≤ᵇ 2 does not hold:

_ : (2 ≤ᵇ 4)  true
_ =
  begin
    2 ≤ᵇ 4
  ≡⟨⟩
    1 ≤ᵇ 3
  ≡⟨⟩
    0 ≤ᵇ 2
  ≡⟨⟩
    true
  

_ : (4 ≤ᵇ 2)  false
_ =
  begin
    4 ≤ᵇ 2
  ≡⟨⟩
    3 ≤ᵇ 1
  ≡⟨⟩
    2 ≤ᵇ 0
  ≡⟨⟩
    false
  

In the first case, it takes two steps to reduce the first argument to zero, and one more step to compute true, corresponding to the two uses of s≤s and the one use of z≤n when providing evidence that 2 ≤ 4. In the second case, it takes two steps to reduce the second argument to zero, and one more step to compute false, corresponding to the two uses of s≤s and the one use of () when showing there can be no evidence that 4 ≤ 2.

Relating evidence and computation

We would hope to be able to show these two approaches are related, and indeed we can. First, we define a function that lets us map from the computation world to the evidence world:

T : Bool  Set
T true   =  
T false  =  

Recall that is the unit type which contains the single element tt, and the is the empty type which contains no values. (Also note that T is a capital letter t, and distinct from .) If b is of type Bool, then tt provides evidence that T b holds if b is true, while there is no possible evidence that T b holds if b is false.

Another way to put this is that T b is inhabited exactly when b ≡ true is inhabited. In the forward direction, we need to do a case analysis on the boolean b:

T→≡ :  (b : Bool)  T b  b  true
T→≡ true tt   =  refl
T→≡ false ()

If b is true then T b is inhabited by tt and b ≡ true is inhabited by refl, while if b is false then T b in uninhabited.

In the reverse direction, there is no need for a case analysis on the boolean b:

≡→T :  {b : Bool}  b  true  T b
≡→T refl  =  tt

If b ≡ true is inhabited by refl we know that b is true and hence T b is inhabited by tt.

Now we can show that T (m ≤ᵇ n) is inhabited exactly when m ≤ n is inhabited.

In the forward direction, we consider the three clauses in the definition of _≤ᵇ_:

≤ᵇ→≤ :  (m n : )  T (m ≤ᵇ n)  m  n
≤ᵇ→≤ zero    n       tt  =  z≤n
≤ᵇ→≤ (suc m) zero    ()
≤ᵇ→≤ (suc m) (suc n) t   =  s≤s (≤ᵇ→≤ m n t)

In the first clause, we immediately have that zero ≤ᵇ n is true, so T (m ≤ᵇ n) is evidenced by tt, and correspondingly m ≤ n is evidenced by z≤n. In the middle clause, we immediately have that suc m ≤ᵇ zero is false, and hence T (m ≤ᵇ n) is empty, so we need not provide evidence that m ≤ n, which is just as well since there is no such evidence. In the last clause, we have that suc m ≤ᵇ suc n recurses to m ≤ᵇ n. We let t be the evidence of T (suc m ≤ᵇ suc n) if it exists, which, by definition of _≤ᵇ_, will also be evidence of T (m ≤ᵇ n). We recursively invoke the function to get evidence that m ≤ n, which s≤s converts to evidence that suc m ≤ suc n.

In the reverse direction, we consider the possible forms of evidence that m ≤ n:

≤→≤ᵇ :  {m n : }  m  n  T (m ≤ᵇ n)
≤→≤ᵇ z≤n        =  tt
≤→≤ᵇ (s≤s m≤n)  =  ≤→≤ᵇ m≤n

If the evidence is z≤n then we immediately have that zero ≤ᵇ n is true, so T (m ≤ᵇ n) is evidenced by tt. If the evidence is s≤s applied to m≤n, then suc m ≤ᵇ suc n reduces to m ≤ᵇ n, and we may recursively invoke the function to produce evidence that T (m ≤ᵇ n).

The forward proof has one more clause than the reverse proof, precisely because in the forward proof we need clauses corresponding to the comparison yielding both true and false, while in the reverse proof we only need clauses corresponding to the case where there is evidence that the comparison holds. This is exactly why we tend to prefer the evidence formulation to the computation formulation, because it allows us to do less work: we consider only cases where the relation holds, and can ignore those where it does not.

On the other hand, sometimes the computation formulation may be just what we want. Given a non-obvious relation over large values, it might be handy to have the computer work out the answer for us. Fortunately, rather than choosing between evidence and computation, there is a way to get the benefits of both.

The best of both worlds

A function that returns a boolean returns exactly a single bit of information: does the relation hold or does it not? Conversely, the evidence approach tells us exactly why the relation holds, but we are responsible for generating the evidence. But it is easy to define a type that combines the benefits of both approaches. It is called Dec A, where Dec is short for decidable:

data Dec (A : Set) : Set where
  yes :   A  Dec A
  no  : ¬ A  Dec A

Like booleans, the type has two constructors. A value of type Dec A is either of the form yes x, where x provides evidence that A holds, or of the form no ¬x, where ¬x provides evidence that A cannot hold (that is, ¬x is a function which given evidence of A yields a contradiction).

For example, we define a function _≤?_ which given two numbers decides whether one is less than or equal to the other, and provides evidence to justify its conclusion.

First, we introduce two functions useful for constructing evidence that an inequality does not hold:

¬s≤z :  {m : }  ¬ (suc m  zero)
¬s≤z ()

¬s≤s :  {m n : }  ¬ (m  n)  ¬ (suc m  suc n)
¬s≤s ¬m≤n (s≤s m≤n) = ¬m≤n m≤n

The first of these asserts that ¬ (suc m ≤ zero), and follows by absurdity, since any evidence of inequality has the form zero ≤ n or suc m ≤ suc n, neither of which match suc m ≤ zero. The second of these takes evidence ¬m≤n of ¬ (m ≤ n) and returns a proof of ¬ (suc m ≤ suc n). Any evidence of suc m ≤ suc n must have the form s≤s m≤n where m≤n is evidence that m ≤ n. Hence, we have a contradiction, evidenced by ¬m≤n m≤n.

Using these, it is straightforward to decide an inequality:

_≤?_ :  (m n : )  Dec (m  n)
zero  ≤? n                   =  yes z≤n
suc m ≤? zero                =  no ¬s≤z
suc m ≤? suc n with m ≤? n
...               | yes m≤n  =  yes (s≤s m≤n)
...               | no ¬m≤n  =  no (¬s≤s ¬m≤n)

As with _≤ᵇ_, the definition has three clauses. In the first clause, it is immediate that zero ≤ n holds, and it is evidenced by z≤n. In the second clause, it is immediate that suc m ≤ zero does not hold, and it is evidenced by ¬s≤z. In the third clause, to decide whether suc m ≤ suc n holds we recursively invoke m ≤? n. There are two possibilities. In the yes case it returns evidence m≤n that m ≤ n, and s≤s m≤n provides evidence that suc m ≤ suc n. In the no case it returns evidence ¬m≤n that ¬ (m ≤ n), and ¬s≤s ¬m≤n provides evidence that ¬ (suc m ≤ suc n).

When we wrote _≤ᵇ_, we had to write two other functions, ≤ᵇ→≤ and ≤→≤ᵇ, in order to show that it was correct. In contrast, the definition of _≤?_ proves itself correct, as attested to by its type. The code of _≤?_ is far more compact than the combined code of _≤ᵇ_, ≤ᵇ→≤, and ≤→≤ᵇ. As we will later show, if you really want the latter three, it is easy to derive them from _≤?_.

We can use our new function to compute the evidence that earlier we had to think up on our own:

_ : 2 ≤? 4  yes (s≤s (s≤s z≤n))
_ = refl

_ : 4 ≤? 2  no (¬s≤s (¬s≤s ¬s≤z))
_ = refl

You can check that Agda will indeed compute these values. Typing C-c C-n and providing 2 ≤? 4 or 4 ≤? 2 as the requested expression causes Agda to print the values given above.

(A subtlety: if we do not define ¬s≤z and ¬s≤s as top-level functions, but instead use inline anonymous functions then Agda may have trouble normalising evidence of negation.)

Analogous to the function above, define a function to decide strict inequality:

postulate
  _<?_ :  (m n : )  Dec (m < n)
-- Your code goes here

Exercise _≡ℕ?_ (practice)

Define a function to decide whether two naturals are equal:

postulate
  _≡ℕ?_ :  (m n : )  Dec (m  n)
-- Your code goes here

Decidables from booleans, and booleans from decidables

Curious readers might wonder if we could reuse the definition of m ≤ᵇ n, together with the proofs that it is equivalent to m ≤ n, to show decidability. Indeed, we can do so as follows:

_≤?′_ :  (m n : )  Dec (m  n)
m ≤?′ n with m ≤ᵇ n | ≤ᵇ→≤ m n | ≤→≤ᵇ {m} {n}
...        | true   | p        | _            = yes (p tt)
...        | false  | _        | ¬p           = no ¬p

If m ≤ᵇ n is true then ≤ᵇ→≤ yields a proof that m ≤ n holds, while if it is false then ≤→≤ᵇ takes a proof that m ≤ n holds into a contradiction.

The triple binding of the with clause in this proof is essential. If instead we wrote:

_≤?″_ : ∀ (m n : ℕ) → Dec (m ≤ n)
m ≤?″ n with m ≤ᵇ n
... | true   =  yes (≤ᵇ→≤ m n tt)
... | false  =  no (≤→≤ᵇ {m} {n})

then Agda would make two complaints, one for each clause:

⊤ !=< (T (m ≤ᵇ n)) of type Set
when checking that the expression tt has type T (m ≤ᵇ n)

T (m ≤ᵇ n) !=< ⊥ of type Set
when checking that the expression ≤→≤ᵇ {m} {n} has type ¬ m ≤ n

Putting the expressions into the with clause permits Agda to exploit the fact that T (m ≤ᵇ n) is when m ≤ᵇ n is true, and that T (m ≤ᵇ n) is when m ≤ᵇ n is false.

However, overall it is simpler to just define _≤?_ directly, as in the previous section. If one really wants _≤ᵇ_, then it and its properties are easily derived from _≤?_, as we will now show.

Erasure takes a decidable value to a boolean:

⌊_⌋ :  {A : Set}  Dec A  Bool
 yes x   =  true
 no ¬x   =  false

Using erasure, we can easily derive _≤ᵇ_ from _≤?_:

_≤ᵇ′_ :     Bool
m ≤ᵇ′ n  =   m ≤? n 

Further, if D is a value of type Dec A, then T ⌊ D ⌋ is inhabited exactly when A is inhabited:

toWitness :  {A : Set} {D : Dec A}  T  D   A
toWitness {A} {yes x} tt  =  x
toWitness {A} {no ¬x} ()

fromWitness :  {A : Set} {D : Dec A}  A  T  D 
fromWitness {A} {yes x} _  =  tt
fromWitness {A} {no ¬x} x  =  ¬x x

Using these, we can easily derive that T (m ≤ᵇ′ n) is inhabited exactly when m ≤ n is inhabited:

≤ᵇ′→≤ :  {m n : }  T (m ≤ᵇ′ n)  m  n
≤ᵇ′→≤  =  toWitness

≤→≤ᵇ′ :  {m n : }  m  n  T (m ≤ᵇ′ n)
≤→≤ᵇ′  =  fromWitness

In summary, it is usually best to eschew booleans and rely on decidables. If you need booleans, they and their properties are easily derived from the corresponding decidables.

Logical connectives

Most readers will be familiar with the logical connectives for booleans. Each of these extends to decidables.

The conjunction of two booleans is true if both are true, and false if either is false:

infixr 6 _∧_

_∧_ : Bool  Bool  Bool
true   true  = true
false  _     = false
_      false = false

In Emacs, the left-hand side of the third equation displays in grey, indicating that the order of the equations determines which of the second or the third can match. However, regardless of which matches the answer is the same.

Correspondingly, given two decidable propositions, we can decide their conjunction:

infixr 6 _×-dec_

_×-dec_ :  {A B : Set}  Dec A  Dec B  Dec (A × B)
yes x ×-dec yes y = yes  x , y 
no ¬x ×-dec _     = no λ{  x , y   ¬x x }
_     ×-dec no ¬y = no λ{  x , y   ¬y y }

The conjunction of two propositions holds if they both hold, and its negation holds if the negation of either holds. If both hold, then we pair the evidence for each to yield evidence of the conjunct. If the negation of either holds, assuming the conjunct will lead to a contradiction.

Again in Emacs, the left-hand side of the third equation displays in grey, indicating that the order of the equations determines which of the second or the third can match. This time the answer is different depending on which matches; if both conjuncts fail to hold we pick the first to yield the contradiction, but it would be equally valid to pick the second.

The disjunction of two booleans is true if either is true, and false if both are false:

infixr 5 _∨_

_∨_ : Bool  Bool  Bool
true   _      = true
_      true   = true
false  false  = false

In Emacs, the left-hand side of the second equation displays in grey, indicating that the order of the equations determines which of the first or the second can match. However, regardless of which matches the answer is the same.

Correspondingly, given two decidable propositions, we can decide their disjunction:

infixr 5 _⊎-dec_

_⊎-dec_ :  {A B : Set}  Dec A  Dec B  Dec (A  B)
yes x ⊎-dec _     = yes (inj₁ x)
_     ⊎-dec yes y = yes (inj₂ y)
no ¬x ⊎-dec no ¬y = no λ{ (inj₁ x)  ¬x x ; (inj₂ y)  ¬y y }

The disjunction of two propositions holds if either holds, and its negation holds if the negation of both hold. If either holds, we inject the evidence to yield evidence of the disjunct. If the negation of both hold, assuming either disjunct will lead to a contradiction.

Again in Emacs, the left-hand side of the second equation displays in grey, indicating that the order of the equations determines which of the first or the second can match. This time the answer is different depending on which matches; if both disjuncts hold we pick the first, but it would be equally valid to pick the second.

The negation of a boolean is false if its argument is true, and vice versa:

not : Bool  Bool
not true  = false
not false = true

Correspondingly, given a decidable proposition, we can decide its negation:

¬? :  {A : Set}  Dec A  Dec (¬ A)
¬? (yes x)  =  no (¬¬-intro x)
¬? (no ¬x)  =  yes ¬x

We simply swap yes and no. In the first equation, the right-hand side asserts that the negation of ¬ A holds, in other words, that ¬ ¬ A holds, which is an easy consequence of the fact that A holds.

There is also a slightly less familiar connective, corresponding to implication:

_⊃_ : Bool  Bool  Bool
_      true   =  true
false  _      =  true
true   false  =  false

One boolean implies another if whenever the first is true then the second is true. Hence, the implication of two booleans is true if the second is true or the first is false, and false if the first is true and the second is false. In Emacs, the left-hand side of the second equation displays in grey, indicating that the order of the equations determines which of the first or the second can match. However, regardless of which matches the answer is the same.

Correspondingly, given two decidable propositions, we can decide if the first implies the second:

_→-dec_ :  {A B : Set}  Dec A  Dec B  Dec (A  B)
_     →-dec yes y  =  yes  _  y)
no ¬x →-dec _      =  yes  x  ⊥-elim (¬x x))
yes x →-dec no ¬y  =  no  f  ¬y (f x))

The implication holds if either the second holds or the negation of the first holds, and its negation holds if the first holds and the negation of the second holds. Evidence for the implication is a function from evidence of the first to evidence of the second. If the second holds, the function returns the evidence for it. If the negation of the first holds, the function takes the evidence of the first and derives a contradiction. If the first holds and the negation of the second holds, given evidence of the implication we must derive a contradiction; we apply the evidence of the implication f to the evidence of the first x, yielding a contradiction with the evidence ¬y of the negation of the second.

Again in Emacs, the left-hand side of the second equation displays in grey, indicating that the order of the equations determines which of the first or the second can match. This time the answer is different depending on which matches; but either is equally valid.

Exercise erasure (practice)

Show that erasure relates corresponding boolean and decidable operations:

postulate
  ∧-× :  {A B : Set} (x : Dec A) (y : Dec B)   x    y    x ×-dec y 
  ∨-⊎ :  {A B : Set} (x : Dec A) (y : Dec B)   x    y    x ⊎-dec y 
  not-¬ :  {A : Set} (x : Dec A)  not  x    ¬? x 

Give analogues of the _⇔_ operation from Chapter Isomorphism, operation on booleans and decidables, and also show the corresponding erasure:

postulate
  _iff_ : Bool  Bool  Bool
  _⇔-dec_ :  {A B : Set}  Dec A  Dec B  Dec (A  B)
  iff-⇔ :  {A B : Set} (x : Dec A) (y : Dec B)   x  iff  y    x ⇔-dec y 
-- Your code goes here

Proof by reflection

Let’s revisit our definition of monus from Chapter Naturals. If we subtract a larger number from a smaller number, we take the result to be zero. We had to do something, after all. What could we have done differently? We could have defined a guarded version of minus, a function which subtracts n from m only if n ≤ m:

minus : (m n : ) (n≤m : n  m)  
minus m       zero    _         = m
minus (suc m) (suc n) (s≤s m≤n) = minus m n m≤n

Unfortunately, it is painful to use, since we have to explicitly provide the proof that n ≤ m:

_ : minus 5 3 (s≤s (s≤s (s≤s z≤n)))  2
_ = refl

We cannot solve this problem in general, but in the scenario above, we happen to know the two numbers statically. In that case, we can use a technique called proof by reflection. Essentially, we can ask Agda to run the decidable equality n ≤? m while type checking, and make sure that n ≤ m!

We do this by using a feature of implicits. Agda will fill in an implicit of a record type if it can fill in all its fields. So Agda will always manage to fill in an implicit of an empty record type, since there aren’t any fields after all. This is why is defined as an empty record.

The trick is to have an implicit argument of the type T ⌊ n ≤? m ⌋. Let’s go through what this means step-by-step. First, we run the decision procedure, n ≤? m. This provides us with evidence whether n ≤ m holds or not. We erase the evidence to a boolean. Finally, we apply T. Recall that T maps booleans into the world of evidence: true becomes the unit type , and false becomes the empty type . Operationally, an implicit argument of this type works as a guard.

  • If n ≤ m holds, the type of the implicit value reduces to . Agda then happily provides the implicit value.
  • Otherwise, the type reduces to , which Agda has no chance of providing, so it will throw an error. For instance, if we call 3 - 5 we get _n≤m_254 : ⊥.

We obtain the witness for n ≤ m using toWitness, which we defined earlier:

_-_ : (m n : ) {n≤m : T  n ≤? m }  
_-_ m n {n≤m} = minus m n (toWitness n≤m)

We can safely use _-_ as long as we statically know the two numbers:

_ : 5 - 3  2
_ = refl

It turns out that this idiom is very common. The standard library defines a synonym for T ⌊ ? ⌋ called True:

True :  {Q}  Dec Q  Set
True Q = T  Q 

Exercise False

Give analogues of True, toWitness, and fromWitness which work with negated properties. Call these False, toWitnessFalse, and fromWitnessFalse.

Standard Library

import Data.Bool.Base using (Bool; true; false; T; _∧_; _∨_; not)
import Data.Nat using (_≤?_)
import Relation.Nullary using (Dec; yes; no)
import Relation.Nullary.Decidable using (⌊_⌋; True; toWitness; fromWitness)
import Relation.Nullary.Negation using (¬?)
import Relation.Nullary.Product using (_×-dec_)
import Relation.Nullary.Sum using (_⊎-dec_)
import Relation.Binary using (Decidable)

Unicode

∧  U+2227  LOGICAL AND (\and, \wedge)
∨  U+2228  LOGICAL OR (\or, \vee)
⊃  U+2283  SUPERSET OF (\sup)
ᵇ  U+1D47  MODIFIER LETTER SMALL B  (\^b)
⌊  U+230A  LEFT FLOOR (\cll)
⌋  U+230B  RIGHT FLOOR (\clr)