Correct-by-construction programming in Agda

Lecture 4: (Non-)termination

Jesper Cockx

3 September 2019

"I’ll save the world…

tomorrow."

– unknown

Totality checking

Partial functions in type theory

What happens if we have a partial function in Agda?

  • Theory would become inconsistent!
absurd : ⊥
absurd = absurd
  • Typechecker would crash or loop!
f : ℕ → ℕ
f zero = 42

test : Vec ℕ (f 1)
test = []

⇒ Partial functions must be ruled out!

Checking totality

  1. Completeness of pattern matching
  2. Structural recursion of recursive functions
  3. Strict positivity of inductive datatypes
  4. Consistency of universe levels

2-4 together ensure normalization of well-typed terms

Primitive recursion

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

Structural recursion

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)

Coinduction

A frequently asked question

“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.

Coinduction in Agda

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

Mixing induction and coinduction (1/2)

  mutual
    record Coℕ′ : Set where
      coinductive
      field
        force : Coℕ

    data Coℕ : Set where
      zero : Coℕ
      suc  : Coℕ′  Coℕ
  open Coℕ′ public

Mixing induction and coinduction (2/2)

  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

Dealing with infinite computations

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?

Going carbon-free with the Delay monad

A value of type Delay A is

  • either a value of type A produced now
  • or a computation of type Delay A producing a value later

The Delay monad captures the effect of non-termination

The Delay monad: definition

  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

Unrolling a Delayed value

  unroll : {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

Sized types

Using sizes to prove termination

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.

The type 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!

The sized delay monad

  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.

Interpreting well-typed WHILE programs

WHILE statements can have two effects:

  • Modify the environment ⇒ State monad
  • Go into a loop ⇒ Delay monad

We combine both effects in the Exec monad.

The Exec monad

  record 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.

Exercise

Now you should be ready to add a bigger new feature:

  • A new control operator: if statements, do/while loops, for, switch, …
  • New types: char, bool, …
  • New programming concepts: function calls, pointers (hard!), …

Extend the syntax, the typechecker, and the interpreter with rules for your new feature.