2

I have defined infinite streams as follows:

record Stream (A : Set) : Set where
    coinductive
    field head : A
    field tail : Stream A

and an inductive type which shows that some element in a stream eventually satisfies a predicate:

data Eventually {A} (P : A -> Set) (xs : Stream A) : Set where
    here  : P (head xs) -> Eventually P xs
    there : Eventually P (tail xs) -> Eventually P xs

I would like to write a function which skips over elements of the stream until the head of the stream satisfies a predicate. To ensure termination, we must know that an element eventually satisfies the predicate, else we could loop forever. Hence, the definition of Eventually must be passed as an argument. Furthermore, the function should not computationally depend on the Eventually predicate, as it is just there to prove termination, so I would like it to be an erased argument.

dropUntil : {A : Set} {P : A -> Set} (decide : ∀ x → Dec (P x)) → (xs : Stream A) → @0 Eventually P xs → Stream A
dropUntil decide xs ev with decide (head xs)
... | yes prf = xs
... | no contra = dropUntil decide (tail xs) ?

Here is the problem - I would like to fill in the hole in the definition. From contra in scope, we know that the head of the stream does not satisfy P, and hence by definition of eventually, some element in the tail of the stream must satisfy P. If Eventually wasn't erased in this context, we could simply pattern match on the predicate, and prove the here case impossible. Normally in these scenarios I would write an erased auxiliary function, on the lines of:

@0 eventuallyInv : ∀ {A} {P : A → Set} {xs : Stream A} → Eventually P xs → ¬ P (head xs) → Eventually P (tail xs)
eventuallyInv (here x) contra with contra x
... | ()
eventuallyInv (there ev) contra = ev

The problem with this approach is that the Eventually proof is the structurally recursive argument in dropUntil, and calling this auxiliary function does not pass the termination checker as Agda does not "look inside" the function definition.

Another approach I tried is inlining the above erased function into the definition of dropUntil. Unfortunately, I had no luck with this approach either - using the definition of case ... of like described here https://agda.readthedocs.io/en/v2.5.2/language/with-abstraction.html did not pass the termination checker either.

I have written an equivalent program in Coq which is accepted (using Prop rather than erased types), so I am confident that my reasoning is correct. The main reason why Coq accepted the definition and Agda doesn't is that Coq's termination checker expands function definitions, and hence the "auxiliary erased function" approach succeeds.

EDIT:

This is my attempt using sized types, however it does not pass the termination checker and I can't figure out why.

record Stream (A : Set) : Set where
    coinductive
    field
        head : A
        tail : Stream A

open Stream


data Eventually {A} (P : A → Set) (xs : Stream A) : Size → Set where
    here : ∀ {i} → P (head xs) → Eventually P xs (↑ i)
    there : ∀ {i} → Eventually P (tail xs) i → Eventually P xs (↑ i)


@0 eventuallyInv : ∀ {A P i} {xs : Stream A} → Eventually P xs (↑ i) → ¬ P (head xs) → Eventually P (tail xs) i
eventuallyInv (here p) ¬p with ¬p p
... | ()
eventuallyInv (there ev) ¬p = ev


dropUntil : ∀ {A P i} → (∀ x → Dec (P x)) → (xs : Stream A) → @0 Eventually P xs (↑ i) → Stream A
dropUntil decide xs ev with decide (head xs)
... | yes p = xs
... | no ¬p = dropUntil decide (tail xs) (eventuallyInv ev ¬p)
Eayus
  • 65
  • 4
  • I'm having a hard time understanding why your function should not computationally depend on the proof because it contains exactly the information you need to write your function. – MrO Jul 29 '20 at 14:07
  • The `Eventually` proof specifies that there _is_ an element satisfying a predicate, but does not necessarily locate the _first_ one - whereas dropUntil should stop at the first element satisfying the predicate. Therefore I need to call `decide` on every element anyway, and so inspecting the `Eventually` is redundant and therefore can be erased. – Eayus Jul 29 '20 at 14:39
  • The idea of the `Eventually` predicate on streams is taken roughly from this paper: https://hal.inria.fr/inria-00070658/document , which probably justifies its use better than I have. – Eayus Jul 29 '20 at 14:44
  • I deleted my answer since it seems that I have missed something in your requirements. – MrO Jul 29 '20 at 17:18
  • To clarify, the streams must remain coinductive, as described here: https://agda.readthedocs.io/en/v2.6.1/language/coinduction.html (preferably in the "new style" coinduction) – Eayus Jul 29 '20 at 17:52
  • Have you tried using the streams from the std library ? – MrO Jul 29 '20 at 17:52
  • I have not. I just noticed the Streams in the standard library use sized types, which may be helpful for proving the result of `eventuallyInv` is smaller than its argument - I'll have a look to see if I can get that to work in a bit. – Eayus Jul 29 '20 at 18:29
  • I put some more thoughts in your issue and, while i'm no coinduction expert, I feel like having Eventually as an erased argument is opposed to using it for termination, because using it for termination requires to have it structurally decrease, which seamingly cannot be done in an erased (or irrelevant) context. – MrO Jul 31 '20 at 13:48
  • Right - this is the exact problem. Unfortunately Agda has no suitable `match` construct for pattern matching in definition bodies (`case .. of ...` loses the connection between the original value), so the definition cannot be inlined. In addition, Agda does not normalise expressions before termination checking, so writing it as a separate function will not pass the termination checker. I believe it may be possible to use sized types to show termination, as the erased function can show it produces a smaller `Eventually`, but I haven't been able to get this to work (see edit). – Eayus Aug 01 '20 at 09:40
  • The issue I can see in your edit is that you try to size the Eventually predicate, which you cannot use either for the same reason mentioned before. In the std lib, the streams themselves are sized, which might be the way to go. On another note, what bothers me with your Eventually predicate is that, to compute it, you need to use the stream beforehand in a possibly non-terminating way which kind of make the whole thing pointless doesnt it ? Especially if you're not willing to use it afterwards ? – MrO Aug 01 '20 at 11:24
  • Since there is quite a lot of confusion surrounding this question (and many possible approaches to solving it), I will post some more atomic questions and come back to solve this later. – Eayus Aug 01 '20 at 21:32

1 Answers1

1

In your case you can work with a weaker notion of Eventually which matches what dropUntil actually needs to know. It's also single constructor so you can match on it even when erased.

  data Eventually' {A} (P : A -> Set) (xs : Stream A) : Set where
    next : (¬ P (head xs) → Eventually' P (tail xs)) → Eventually' P xs

  eventuallyInv : ∀ {A} {P : A → Set} {xs : Stream A} → (ev : Eventually P xs) → Eventually' P xs
  eventuallyInv (here p) = next \ np → ⊥-elim (np p)
  eventuallyInv (there ev) = next \ np → eventuallyInv ev

  dropUntil' : {A : Set} {P : A -> Set} (decide : ∀ x → Dec (P x)) → (xs : Stream A) → @0 Eventually' P xs → Stream A
  dropUntil' decide xs (next ev) with decide (head xs)
  ... | yes prf = xs
  ... | no contra = dropUntil' decide (tail xs) (ev contra)

  dropUntil : {A : Set} {P : A -> Set} (decide : ∀ x → Dec (P x)) → (xs : Stream A) → @0 Eventually P xs → Stream A
  dropUntil decide xs ev = dropUntil' decide xs (eventuallyInv ev)
Saizan
  • 1,391
  • 8
  • 4