Reputation: 89053
I'm learning Agda using Philip Wadler's Programming Language Foundations In Agda, and I can't figure out how to convince the compiler that a computation terminates.
I've got types for unary and binary naturals:
data ℕ : Set where
zero : ℕ
suc : ℕ → ℕ
data Bin : Set where
⟨⟩ : Bin
_O : Bin → Bin
_I : Bin → Bin
And I wrote a function to convert between the two representations (using some helpers):
-- try to count to a given power of two
--
-- to-count m t f n =
-- t (n - 2^m) if n >= 2^m
-- (f * 2^m) + n otherwise
to-count : ℕ → (ℕ → Bin) → Bin → ℕ → Bin
to-count zero t f zero = f
to-count zero t f (suc n) = t n
to-count (suc m) t f n = to-count m (to-count m t (f I)) (f O) n
-- keep trying to count bigger and bigger powers of two
to-next : ℕ → ℕ → Bin
to-next m = to-count m (to-next (suc m)) (⟨⟩ I)
to : ℕ → Bin
to = to-count zero (to-next zero) ⟨⟩
Later, when trying to prove that my conversion is faithful:
import Relation.Binary.PropositionalEquality as Eq
open Eq using (_≡_; refl; cong)
open Eq.≡-Reasoning using (begin_; _≡⟨⟩_; _≡⟨_⟩_; _∎)
_ : to zero ≡ ⟨⟩
_ = refl
_ : to (suc zero) ≡ ⟨⟩ I
_ = refl
The compiler complains that termination checking failed:
Checking Naturals (Naturals.agda).
Naturals.agda:23,1-24,48
Termination checking failed for the following functions:
to-next
Problematic calls:
to-next (suc m)
(at Naturals.agda:24,25-32)
Naturals.agda:37,5-9
to-next zero zero != ⟨⟩ I of type Bin
when checking that the expression refl has type
to (suc zero) ≡ (⟨⟩ I)
What are some strategies I can use to help convince the compiler that it terminates?
Upvotes: 1
Views: 395
Reputation: 5406
Using pragma is not how you need to convince the compiler that the function terminates.
The compiler indicated the problematic call: to-next (suc m)
cannot be seen as unused in the cases you think, and obviously it creates a structurally bigger value than on input.
A way to deal with this problem is express the construction of Bin
from ℕ differently.
inc-bin : Bin -> Bin
inc-bin ⟨⟩ = ⟨⟩ I
inc-bin (bb O) = bb I
inc-bin (bb I) = (inc-bin bb) O
to-bin-daft : ℕ -> Bin
to-bin-daft zero = b O
to-bin-daft (suc m) = inc-bin (to-bin-daft m)
This is "daft", as it literally increments Bin
by one at a time, for every suc
, but more complex algorithms involving, say, division by 2, require evidence that the result of division is smaller than the input.
Upvotes: 2
Reputation: 89053
Not sure if this is the most idiomatic solution, but I got it working using the TERMINATING
pragma:
{-# TERMINATING #-}
to-next : ℕ → ℕ → Bin
to-next m = to-count m (to-next (suc m)) (⟨⟩ I)
Upvotes: 1