📥 Download raw
module Padova2025.ProvingBasics.Termination.Gas where

Gas

open import Padova2025.ProgrammingBasics.Naturals.Base
open import Padova2025.ProgrammingBasics.Naturals.Arithmetic
open import Padova2025.ProvingBasics.Equality.Base

To be convinced that a recursion eventually terminates, Agda’s termination checker needs to observe that the recursive call is on structurally smaller arguments, such as x being structurally smaller than succ x. Custom ordering relations, no matter how relevant to the situation at hand, are not consulted by Agda’s termination checker.

To satisfy the termination checker, we can introduce an additional argument whose only purpose is to get structurally smaller at each step.

digits₃ :   
digits₃ x = go (succ x) x
  where
  go : (gas : )  (x : )  
  go zero       x          = zero  -- bailout, give up, should never be reached
  go (succ gas) zero       = zero
  go (succ gas) x@(succ _) = succ (go gas (half x))

This definition works because an initial gas of succ x is always sufficient to compute the correct answer. (Using x as initial gas would also work, but only because we put zero in the bailout case.)

However, the proof that digits₃ x ≡ succ (digits₃ (half x)) for positive x is quite involved.

digits₃-eq : (x : ℕ) → digits₃ (succ x) ≡ succ (digits₃ (half (succ x)))
digits₃-eq x = {!!}

A much better way is provided by the more sophisticated kind of gas discussed in the next module.

Tagging the exceptional case

We were able to imagine some (arbitrary) definition in the bailout case above, because the codomain is inhabited. In other cases, for instance when the codomain is some type of witnesses, no suitable “error value” might be at hand. To make the gas method work, we then use a more explicit mechanism for signaling the bailout case.

data Maybe (A : Set) : Set where
  nothing : Maybe A
  just    : A  Maybe A
digits₃' :   Maybe 
digits₃' x = go (succ x) x
  where
  go : (gas : )  (x : )  Maybe 
  go zero       x          = nothing  -- bailout
  go (succ gas) zero       = just zero
  go (succ gas) x@(succ _) with go gas (half x)
  ... | nothing = nothing
  ... | just y  = just (succ y)

In this version, the original simple structure of the recursive call (succ (digits (half x))) has been lost; we can try to recover it by using the _>>=_ operator.

infixl 1 _>>=_
_>>=_ : {A B : Set}  Maybe A  (A  Maybe B)  Maybe B
nothing >>= f = nothing
just x  >>= f = f x
digits₃'' :   Maybe 
digits₃'' x = go (succ x) x
  where
  go : (gas : )  (x : )  Maybe 
  go zero       x          = nothing  -- bailout
  go (succ gas) zero       = just zero
  go (succ gas) x@(succ _) = go gas (half x) >>= λ y  just (succ y)

Employing so-called do notation, we can improve the presentation a bit more:

digits₃''' :   Maybe 
digits₃''' x = go (succ x) x
  where
  go : (gas : )  (x : )  Maybe 
  go zero       x          = nothing  -- bailout
  go (succ gas) zero       = just zero
  go (succ gas) x@(succ _) = do
    y  go gas (half x)
    just (succ y)

However, proofs involving any of these versions of digits₃ are still quite intricate, and also not particularly insightful. As an exercise, you could try proving that digits₃' x is of the form just _ for every number x.

Prooflessly extracting results in concrete instances

Without having to prove that the results of digits₃' are always of the form just _, we can still extract the results in concrete instances. For instance, to obtain digits₃' five as an actual element of (instead of Maybe ℕ), we can carry out the following preparations…

record 𝟙 : Set where
  constructor tt

module _ {A : Set} where
  From-just : Maybe A  Set
  From-just nothing  = 𝟙
  From-just (just _) = A

  from-just : (m : Maybe A)  From-just m
  from-just nothing  = tt
  from-just (just x) = x

…and then obtain the value as follows.

example-digits-computation : 
example-digits-computation = {!!}

Exercise: Division and modulo

open import Padova2025.ProgrammingBasics.Booleans
open import Padova2025.ProvingBasics.Connectives.Disjunction
open import Padova2025.ProvingBasics.Termination.Ordering

The following snippet of code is intended to implement division, but fails to satisfy the termination checker (and indeed, does not terminate in case the divisor is zero).

infix 7 _/_
_/_ : ℕ → ℕ → ℕ
a / b with ≤-<-connex b a
... | left  b≤a = succ ((a ∸ b) / b)
... | right a<b = zero

Use the brittle gas technique introduced in this module to provide a terminating and correct version of division. You do not need to verify that your implementation is indeed correct. In case the divisor is zero, your function may compute whatever you like.

infix 7 _/_
_/_ :     
_/_ = {!!}

Similarly, the following code is supposed to implement the modulo operation, but fails the termination checker; provide a terminating version.

infix 7 _%_
_%_ : ℕ → ℕ → ℕ
a % b with ≤-<-connex b a
... | left  b≤a = (a ∸ b) % b
... | right a<b = a
infix 7 _%_
_%_ :     
_%_ = {!!}