Skip to content

Commit b777d80

Browse files
committed
Emulate structural recursion with Bound type
Consider the following proof: // ∀ n . n < n+1 !less_than_succ*N : !{case n : Nat} -> Less(n, succ(n)) | succ => less_succ(~n.pred, ~succ(n.pred), less_than_succ(n.pred)) | zero => less_zero(~zero) * ? As is, it can't be completed since, on the halt-case, we'll need to prove `Less(n, succ(n))`, which we can't, since that's what we're trying to prove. In other words, the fact Formality recursive functions need a halt case makes that proof impossible. We could try, for example, returning `Maybe(Less(n, succ(n)))`. That'd be useful in the sense that "if we call the function and it doesn't return nothing, then our theorem holds". But the fact the function can always return `nothing` anytime invalidates it as a proof in the mathematical sense: // ∀ n . n < n+1 !less_than_succ*N : !{case n : Nat} -> Maybe(Less(n, succ(n))) | succ => nothing | zero => nothing * nothing Similarly, we could, in some clever way, return a proof that either `Less(n, succ(n))`, or that the function ran out of gas". But that still accomplishes nothing, since we could just force the function to run out of gas: // ∀ n . n < n+1 !less_than_succ*N : !{case n : Nat} -> TrueIfHalted(N, Less(n, succ(n))) | succ => less_than_succ(zero) | zero => less_than_succ(zero) * did_not_halt In order to solve it in a way that preserves the mathematical meaning of the proof, my idea is to emulate Agda/Coq's structural recursion with a `Bound` type. That type compares the size of the input (here, a `Nat`) with the amount of recursion calls we have left; i.e., our "fuel";, asserting that we always "have enough fuel" to consume the whole input. The proof then goes like ths: T Bound (n : Nat, i : Ind) | bound_succ {~n : Nat, ~i : Ind, k : Bound(n, i)} (succ(n), step(i)) | bound_zero {~i : Ind} (zero , step(i)) // ∀ n . n < n+1 !less_than_succ*N : !{n : Nat, bound : Bound(n, N)} -> Less(n, succ(n)) (case/Bound bound | bound_succ => {e} let r0 = cong_unstep(~i, ~N, ~e) let r1 = less_than_succ(n, k :: rewrite x in Bound(n, x) with r0) let r2 = less_succ(~n, ~succ(n), r1) r2 | bound_zero => {e} less_zero(~zero) : {e : i == step(N)} -> Less(n, succ(n)))(refl(~step(N))) * absurd(absurd_bound(n, bound), ~Less(n, succ(n))) The insight is that, instead of matching on `n` directly, we match on `bound`, which will give us not only the predecessor of `n`, `n - 1`, but also the predecessor of the call count, `N - 1`, together with a proof that it is still larger the number. We can then use this new proof to recurse on `n`'s predecessor. That guarantees that `n` not only always gets smaller, but that it will reach `zero` before our "call count" reaches its limit, essentially emulating Agda's bounded recursion. This Bound only works for Nats, but should generalize. Note: this proof is noisy since it requires us to convince the type-checker that the `N` inside the bound field is the same as the `N` from the function input, which, in turn, required me to prove a bunch of `Ind` theorems. Agda has a built-in syntax that gives you this equality for free, I believe. Should we too?
1 parent 949d3fa commit b777d80

File tree

4 files changed

+73
-23
lines changed

4 files changed

+73
-23
lines changed

Data.Empty.fm

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
T Empty
33

44
// From falsehood, everything follows
5-
absurd : {e : Empty, P : Type} -> P
5+
absurd : {e : Empty, ~P : Type} -> P
66
case/Empty e
77
: P
88

Data.Less.fm

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Data.Empty open
2+
import Data.Nat open
3+
import Data.Unit open
4+
import Induction.Nat open
5+
import Induction.Nat.Unstep open
6+
7+
// a < b
8+
T Less (a : Nat, b : Nat)
9+
| less_succ {~a : Nat, ~b : Nat, l : Less(a, b)} (succ(a), succ(b)) // ∀a b. a < b -> a+1 < b+1
10+
| less_zero {~b : Nat} (zero , succ(b)) // ∀b . 0 < b+1
11+
12+
// `2 < 4`
13+
less.example : Less(0n2, 0n4)
14+
less_succ(~0n1, ~0n3,
15+
less_succ(~0n0, ~0n2,
16+
less_zero(~0n1)))
17+
18+
T Bound (n : Nat, i : Ind)
19+
| bound_succ {~n : Nat, ~i : Ind, k : Bound(n, i)} (succ(n), step(i))
20+
| bound_zero {~i : Ind} (zero , step(i))
21+
22+
absurd_bound : {n : Nat, bound : Bound(n, base)} -> Empty
23+
case/Bound bound
24+
| bound_succ => unit
25+
| bound_zero => unit
26+
: <ind(i, ~{i}Type, #{~r,i}Unit, #Empty)>
27+
28+
// ∀ n . n < n+1
29+
!less_than_succ*N : !{n : Nat, bound : Bound(n, N)} -> Less(n, succ(n))
30+
(case/Bound bound
31+
| bound_succ => {e}
32+
let r0 = cong_unstep(~i, ~N, ~e)
33+
let r1 = less_than_succ(n, k :: rewrite x in Bound(n, x) with r0)
34+
let r2 = less_succ(~n, ~succ(n), r1)
35+
r2
36+
| bound_zero => {e} less_zero(~zero)
37+
: {e : i == step(N)} -> Less(n, succ(n)))(refl(~step(N)))
38+
* absurd(absurd_bound(n, bound), ~Less(n, succ(n)))

Data.LessThan.fm

-22
This file was deleted.

Induction.Nat.Unstep.fm

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import Induction.Nat open
2+
import Data.Bool open
3+
import Relation.Equality open
4+
5+
unstep : {n : Ind} -> Ind
6+
new(~Ind) {~P, S}
7+
dup S = S
8+
let motive = {n} {b : Bool} ->
9+
(%b)(~{b}Type, P(unstep(n)), P(unstep(step(n))))
10+
let case_s = {~n, h, b}
11+
let motive = {b} {h : P(unstep(step(n)))} -> (%b)(~{b}Type, P(unstep(step(n))), P(step(unstep(step(n)))))
12+
let case_t = {x} x
13+
let case_f = S(~(unstep(step(n))))
14+
(%b)(~motive, case_t, case_f, h(false))
15+
dup F = (%n)(~motive, #case_s)
16+
# {Z}
17+
let case_z = {b}
18+
let motive = {b} {n : P(base)} -> (%b)(~{b}Type, P(unstep(base)), P(unstep(step(base))))
19+
let case_t = {z} z
20+
let case_f = {z} z
21+
(%b)(~motive, case_t, case_f, Z)
22+
F(case_z, true)
23+
24+
unstep_step : {i : Ind} -> !unstep(step(i)) == i
25+
let motive = {i} unstep(step(i)) == i
26+
let case_s = # {~n, h} cong(~Ind, ~Ind, ~unstep(step(n)), ~n, ~step, ~h)
27+
dup F = (%i)(~motive, case_s)
28+
# F(refl(~base))
29+
30+
cong_unstep : {~a : Ind, ~b : Ind, ~e : step(a) == step(b)} -> a == b
31+
let r0 = cong(~Ind, ~Ind, ~step(a), ~step(b), ~unstep, ~e)
32+
let r1 = r0 :: rewrite x in x == unstep(step(b)) with <unstep_step(a)>
33+
let r2 = r1 :: rewrite y in a == y with <unstep_step(b)>
34+
r2

0 commit comments

Comments
 (0)