I recently asked this question: An agda proposition used in the type -- what does it mean? and received a very well thought out answer on how to make types implicit and get a re
There is a crucial difference between encoding stuff as propositions and functions. Let's take a look at the _+_
implemented as a relation on numbers and as a function.
Function is trivial, of course:
_+_ : (m n : ℕ) → ℕ
zero + n = n
suc m + n = suc (m + n)
_+_
as a proposition is a ternary relation on numbers. For numbers m
, n
and o
, it should hold precisely when m + n = o
:
data _+_≡_ : ℕ → ℕ → ℕ → Set where
zero : ∀ { n } → zero + n ≡ n
suc : ∀ {m n o} → m + n ≡ o → suc m + n ≡ suc o
We can for example show that 2 + 3 ≡ 5
:
proof : 2 + 3 ≡ 5
proof = suc (suc zero)
Now, functions are more strict about what is allowed: it must pass the termination checker, there must be a unique result, all cases must be covered and so on; in return, they give us computability. Propositions allow redundancy, inconsistency, partial coverage and so on, but to actually show that 2 + 3 = 5
, the programer must be involved.
This is of course a show stopper for your if
: you need to be able to compute its first argument!
But there is hope: we can show that your even
proposition is actually computable (I should say decidable) for every natural number. How do we show that? By writing a function to decide it.
We'll need a negation on propositions:
data ⊥ : Set where
¬_ : Set → Set
¬ A = A → ⊥
Next, we'll write down a data type to tell us whether the propositon holds or not:
data Dec (P : Set) : Set where
yes : P → Dec P
no : ¬ P → Dec P
And lastly, we'll define what it means for even
to be decidable:
EvenDecidable : Set
EvenDecidable = ∀ n → Dec (even n)
This reads: even
is decidable if for any natural number n
we can show that either even n
or ¬ (even n)
. Let's show that this is indeed true:
isEven : EvenDecidable
isEven zero = yes _
isEven (suc zero) = no λ ()
isEven (suc (suc n)) = isEven n
Dec
to Bool
Now, we have:
data Bool : Set where
true false : Bool
if_then_else_ : {A : Set} → Bool → A → A → A
if true then t else _ = t
if false then _ else f = f
and a function isEven
which returns Dec
, not Bool
. We can go from Dec
to Bool
by simply forgetting the proof inside (⌊
can be entered via \clL
, ⌋
via \clR
):
⌊_⌋ : {P : Set} → Dec P → Bool
⌊ yes _ ⌋ = true
⌊ no _ ⌋ = false
And finally, we can use isEven
in if
:
if ⌊ isEven n ⌋ then ? else ?
Next, your g
function: you require a proof of both even n
and even (suc n)
. This won't work, because no-one can possibly give both of those. In fact, we can even derive a contradiction using those:
bad : ∀ n → even n → even (suc n) → ⊥
bad zero p q = q
bad (suc zero) p q = p
bad (suc (suc n)) p q = bad n p q
However, both
bad₂ : ∀ n → even n → even (suc n) → ℕ
bad₂ n p q = div (suc n) q
bad₃ : ∀ n → even n → even (suc n) → ℕ
bad₃ n p q = div n p
typecheck just fine, so I am not entirely sure why you have a problem with the second if
.
Now, we are getting to the main part, the odd
function. If we know that number is not even
, we should be able to show that the successor is even
. We'll reuse the negation from before. agda-mode
can fill right hand sides with just C-c C-a
:
odd : ∀ n → ¬ even n → even (suc n)
odd zero p = p _
odd (suc zero) p = _
odd (suc (suc n)) p = odd n p
Now we have all ingredients to write your g
function:
g : ℕ → ℕ
g n = ?
We'll ask whether the number is even
using the isEven
function:
g : ℕ → ℕ
g n with isEven n
... | isItEven = ?
Now, we'll pattern match on isItEven
to find out what the result was:
g : ℕ → ℕ
g n with isEven n
... | yes e = ?
... | no o = ?
e
is a proof that the number is even
, o
is a proof that it is not even
(it has a type ¬ even n
). e
can be used directly with div
, for o
we need to use the odd
function defined before:
g : ℕ → ℕ
g n with isEven n
... | yes e = div n e
... | no o = div (suc n) (odd n o)
if
for Dec
You cannot implement the above version with just if
, however. Bool
eans do not carry any additional information around; they most certainly don't carry the proof we need. We can write a variant of if
that works with Dec
rather than Bool
. This gives us the ability to distribute the relevant proofs to the then
and else
branches.
if-dec_then_else_ : {P A : Set} → Dec P → (P → A) → (¬ P → A) → A
if-dec yes p then t else _ = t p
if-dec no ¬p then _ else f = f ¬p
Notice that both branches are actually functions that take the proof as its first argument.
g : ℕ → ℕ
g n = if-dec isEven n
then (λ e → div n e)
else (λ o → div (suc n) (odd n o))
We can even create a fine syntax rule for this if
; in this case it's mostly useless, though:
if-syntax = if-dec_then_else_
syntax if-syntax dec (λ yup → yupCase) (λ nope → nopeCase)
= if-dec dec then[ yup ] yupCase else[ nope ] nopeCase
g : ℕ → ℕ
g n = if-dec isEven n
then[ e ] div n e
else[ o ] div (suc n) (odd n o)
with
?Now, I noticed that the with
construct is mentioned in the later parts of the introduction you linked in the previous question. Here's how it works:
Sometimes you need to pattern match on intermediate expressions, such as isEven
in the code above. To do that without with
, you need to actually write two functions:
h₁ : (n : ℕ) → Dec (even n) → ℕ
h₁ n (yes e) = div n e
h₁ n (no o) = div (suc n) (odd n o)
h₂ : ℕ → ℕ
h₂ n = h₁ n (isEven n)
h₂
sets up the expression we would like to pattern match on and h₁
does the actual pattern matching. Now, pattern matching on intermediate expressions like that is fairly frequent so Agda gives us far more compact notation.
h : ℕ → ℕ
h n with isEven n
h n | yes e = div n e
h n | no o = div (suc n) (odd n o)
So, with
behaves as if it added an extra argument which we can pattern match on. You can even use with
on more than one expression at the same time:
i : ℕ → ℕ
i n with isCool n | isBig n
i n | cool | big = ?
We can then pattern match on cool
and big
as if the function had 3 arguments. Now, most of the time the original left hand side stays the same, as the previous functions show (in some cases it can actually be different, but we don't need to deal with that at the moment). For these cases we get a convenient shortcut (especially when the left hand side is long):
i : ℕ → ℕ
i n with isCool n | isBig n
... | cool | big = ?