[Agda] Termination problems with "with" and recursion

Jan Stolarek jan.stolarek at p.lodz.pl
Thu Oct 31 07:47:12 CET 2013


Andreas,

thank you for detailed explanation.

Janek

Dnia środa, 30 października 2013, Andreas Abel napisał:
> Hi Jan, Jesper, and Dominique,
>
> Unfortunately, the termination checker and 'with' do not harmonize.  The
> problem, as already been outlined by Dominique, is that the termination
> checker works on the desugared code, where 'with' has been replaced by
> an auxiliary function.  So, even if in the source code the recursive
> call arguments are looking smaller, the termination checker might not
> see it due to the generated auxiliary function.
>
> On 30.10.2013 15:04, Dominique Devriese wrote:
> > I believe that a with-pattern-match is internally translated to a call
> > of a local helper function with the scrutinee as an extra argument and
> > an appropriate type to use the information learned from the pattern
> > match. Strangely, if I perform this translation manually here:
> >    merge′ : List Nat → List Nat → List Nat
> >    merge′ nil       ys             = ys
> >    merge′ xs        nil            = xs
> >    merge′ (x :: xs) (y :: ys)       = go x xs y ys (order x y)
> >      where go : Nat → List Nat → Nat → List Nat → Order → List Nat
> >            go x xs y ys le = x :: merge xs (y :: ys)
> >            go x xs y ys gt = y :: merge (x :: xs) ys
> >
> > Then the termination checker does succeed.  It seems weird (at least
> > to me) that it doesn't work on the same code without the
> > with-desugaring.
>
> Yes that is weird.  It does not succeed when I try your code.  Note that
> the apparent success might be to the fact that you actually have no
> recursive calls, since your function merge' only calls merge (which is,
> to your bad luck, in scope here).
>
> So, let me emphasize that the termination checker refutes this call
> pattern.
>
>    merge (x :: xs) (y :: ys)
>      --> go x xs y ys ...
>      --> merge xs (y :: ys)
>
> The termination checker sees that in merge-->go, the arguments all
> become smaller, but in go--->merge, one argument becomes bigger.  Since
> it has simplistic, it cannot remember where y and ys came from and that
> taken together, they are actually the same as we started out.  Thus, it
> fails to see that, in the composed call
>
>    merge (x :: xs) (y :: ys) -->---> merge xs (y :: ys),
>
> one argument actually got smaller where the other did not become bigger.
>   If it did not fail to see this, it would accept merge.
>
> There are several ways out of this dilemma:
>
> 1. (preferred) Avoid 'with' when possible.  In your case, there is no
> dependency in the types on (order x y), thus, use an if-then-else-like
> function instead.
>
> 2. Use the trick to put the recursive calls into the 'with' as well.
>
>    merge (x :: xs) (y :: ys) with order x y | merge xs (y :: ys) | merge
> (x :: xs) ys
>    ... | le | call1 | call2 = call1
>    ... | ge | call1 | call2 = call2
>
>    This basically corresponds to the use of an if-like function, only
> that it is "defined on the fly" here.
>
> 3. Define your auxiliary function yourself and in a way that lets the
> termination checker see that your call patterns are ok.
>
> 4. Use sized types (experimental) and --termination-depth=2.  This is
> maybe not ripe for production-use.
>
>    http://gc.codehum.com/p/agda/issues/detail?can=2&q=709
>
> >>> >>The problem with the definition of merge is that the termination
> >>> >> checker looks for one argument to the function that becomes
> >>> >> structurally smaller in all recursive calls.
>
> This is not correct.  Lexicographic orders are also accepted, for instance.
>
> Cheers,
> Andreas




More information about the Agda mailing list