I have a simple coinductive record with a single field of a sum type. Unit
gives us a simple type to play with.
open import Data.Maybe
open import Data.Sum
data Unit : Set where
unit : Unit
record Stream : Set where
coinductive
field
step : Unit ⊎ Stream
open Stream
valid
passes the termination checker:
valid : Maybe Unit → Stream
step (valid x) = inj₂ (valid x)
But say I want to eliminate the Maybe Unit
member and only recurse when I have a just
.
invalid₀ : Maybe Unit → Stream
step (invalid₀ x) = maybe (λ _ → inj₂ (invalid₀ x)) (inj₁ unit) x
Now the termination checker is unhappy!
Termination checking failed for the following functions:
invalid₀
Problematic calls:
invalid₀ x
Why does this not satisfy the termination checker? Is there a way around this or is my conceptual understanding incorrect?
agda --version
yields Agda version 2.6.0-7ae3882
. I am compiling only with the default options.
The output of -v term:100
is here: https://gist.github.com/emilyhorsman/f6562489b82624a5644ed78b21366239
Agda version 2.5.4.2
. Does not fix.--termination-depth=10
. Does not fix.You could make use of sized types here.
open import Data.Maybe
open import Data.Sum
open import Size
data Unit : Set where
unit : Unit
record Stream {i : Size} : Set where
coinductive
field
step : {j : Size< i} → Unit ⊎ Stream {j}
open Stream
valid : Maybe Unit → Stream
step (valid x) = inj₂ (valid x)
invalid₀ : {i : Size} → Maybe Unit → Stream {i}
step (invalid₀ x) = maybe (λ _ → inj₂ (invalid₀ x)) (inj₁ unit) x
_ : step (invalid₀ (nothing)) ≡ inj₁ unit
_ = refl
_ : step (invalid₀ (just unit)) ≡ inj₂ (invalid₀ (just unit))
_ = refl
Being more explicit about the Size
arguments in the definition of invalid₀
:
step (invalid₀ {i} x) {j} = maybe (λ _ → inj₂ (invalid₀ {j} x)) (inj₁ unit) x
where j
has type Size< i
, so the recursive call to invalid₀
is on a “smaller” Size
.
Notice that valid
, which didn't need any “help” to pass termination checking, doesn't need to reason about Size
's at all.
The problem is that Agda cant see that invalid₀
is productive. This is because it is both recursive and not guarded by a constructor. Agda does not look inside the definition of maybe
when deciding whether this is terminating or not.
Here is an implementation that satisfies the termination checker because both branches are guarded by constructors and/or non-recursive:
okay₀ : Maybe Unit → Stream
step (okay₀ x@(just _)) = inj₂ (invalid₀ x)
step (okay₀ nothing) = inj₁ unit
The important part is the recursive just
case has the constructor inj₂
as the top level of the expression.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With