Lecture 4: (Non-)termination
Jesper Cockx
3 September 2019
"I’ll save the world…
tomorrow."
– unknown
What happens if we have a partial function in Agda?
absurd : ⊥
absurd = absurd
f : ℕ → ℕ
f zero = 42
test : Vec ℕ (f 1)
test = []
⇒ Partial functions must be ruled out!
2-4 together ensure normalization of well-typed terms
plus : ℕ → ℕ → ℕ plus zero m = m plus (suc n) m = suc (plus n m) natEq : ℕ → ℕ → Bool natEq zero zero = true natEq zero (suc m) = false natEq (suc n) zero = false natEq (suc n) (suc m) = natEq n m
fib : ℕ → ℕ fib zero = zero fib (suc zero) = suc zero fib (suc (suc n)) = plus (fib n) (fib (suc n))
ack : ℕ → ℕ → ℕ ack zero m = suc m ack (suc n) zero = ack n (suc zero) ack (suc n) (suc m) = ack n (ack (suc n) m)
“If all functions in Agda are total, doesn’t that mean Agda is not Turing-complete?”
Answer: NO! Agda just forces you to be honest about when a function is non-terminating.
A coinductive type = a type with possibly infinitely deep values.
record Stream (A : Set) : Set where coinductive field head : A tail : Stream A open Stream repeat : {A : Set} → A → Stream A head (repeat x) = x tail (repeat x) = repeat x
mutual record Coℕ′ : Set where coinductive field force : Coℕ data Coℕ : Set where zero : Coℕ suc : Coℕ′ → Coℕ open Coℕ′ public
fromℕ : ℕ → Coℕ fromℕ′ : ℕ → Coℕ′ fromℕ zero = zero fromℕ (suc x) = suc (fromℕ′ x) fromℕ′ x .force = fromℕ x infty : Coℕ infty′ : Coℕ′ infty = suc infty′ infty′ .force = infty
Remember: all Agda functions must be total
One way to work around this is by adding ‘fuel’:
step : Term → Term ⊎ Val step = ⋯ eval : ℕ → Term → Maybe Val eval (suc n) t = case (step t) of λ where (inj₁ t') → eval n t (inj₂ v) → just v eval zero t = nothing
Can we do better?
Delay
monadA value of type Delay A
is
A
produced nowDelay A
producing a value laterThe Delay
monad captures the effect of non-termination
mutual record Delay (A : Set) : Set where coinductive field force : Delay' A data Delay' (A : Set) : Set where now : A → Delay' A later : Delay A → Delay' A open Delay public never : {A : Set} → Delay A force never = later never
Delay
ed valueunroll : {A : Set} → ℕ → Delay A → A ⊎ Delay A unroll zero x = inj₂ x unroll (suc n) x = case (force x) of λ where (now v ) → inj₁ v (later d) → unroll n d
Totality requirement: coinductive definitions should be productive: computing each observation should be terminating.
To ensure this, Agda checks that corecursive calls are guarded by constructors, but this is often quite limiting.
A more flexible and modular approach is to use sized types.
Size
Size
≃ abstract version of the natural numbers extended with ∞
For each i : Size
, we have a type Size< i
of sizes smaller than i
.
Note: pattern matching on Size
is not allowed!
mutual record Delay (i : Size) (A : Set) : Set where coinductive constructor delay field force : {j : Size< i} → Delay' j A data Delay' (i : Size) (A : Set) : Set where return' : A → Delay' i A later' : Delay i A → Delay' i A
i
≃ how many more steps are we allowed to observe.
Delay ∞ A
is the type of computations that can take any number of steps.
WHILE statements can have two effects:
State
monadDelay
monadWe combine both effects in the Exec
monad.
Exec
monadrecord Exec {Γ : Cxt} (i : Size) (A : Set) : Set where field runExec : (ρ : Env Γ) → Delay i (A × Env Γ) open Exec public execStm : ∀ {Γ} {i} (s : Stm Γ) → Exec {Γ} i ⊤ execStm = ⋯ execPrg : ∀ {i} (prg : Program) → Delay i ℤ execPrg prg = ⋯
See V3/Interpreter.agda for full code.
Now you should be ready to add a bigger new feature:
if
statements, do/while
loops, for
, switch
, …char
, bool
, …Extend the syntax, the typechecker, and the interpreter with rules for your new feature.