I have a recursive function* that is similar to an "optional map", with the following signature:
omap (f : option Z -> list nat) (l : list Z) : option (list nat)
I defined an equivalent (modulo list reversal) tail-recursive function (omap_tr
below), and I would like to prove that both are equivalent, at least in the case Some
.
I am currently failing to do so, either because my inductive invariant is not strong enough, or because I am not correctly applying the double induction. I wonder if there is a standard technique for this kind of transformation.
*The function has been simplified; for instance None
seems useless here, but it is necessary in the original function.
Code
Here is the code of the (simplified) non-tail-recursive function, along with an example of a function f
:
Fixpoint omap (f : option Z -> list nat) (l : list Z) : option (list nat) :=
match l with
| nil => Some nil
| z :: zr =>
let nr1 := f (Some z) in
let nr2 := match omap f zr with
| None => nil
| Some nr' => nr'
end in
Some (nr1 ++ nr2)
end.
Let f (oz : option Z) : list nat :=
match oz with
| None => nil
| Some z => Z.to_nat z :: nil
end.
For instance, omap f
simply converts Z
integers to nat
integers:
Compute omap f (1 :: 2 :: 3 :: 4 :: nil)%Z.
= Some (1%nat :: 2%nat :: 3%nat :: 4%nat :: nil) : option (list nat)
I performed what I believe is a standard accumulator-based transformation, adding an acc
parameter to both f
and omap
:
Fixpoint omap_tr (f_tr : option Z -> list nat -> list nat) (l : list Z)
(acc : list nat) : option (list nat) :=
match l with
| nil => Some acc
| z :: zr => let nr1 := f_tr (Some z) acc in
omap_tr f_tr zr nr1
end.
Let f_tr rz acc :=
match rz with
| None => acc
| Some z => Z.to_nat z :: acc
end.
It seems to work, despite returning a reversed list. Here's an example of its usage, with a non-empty accumulator:
Compute match omap_tr f_tr (3 :: 4 :: nil)%Z (rev (1 :: 2 :: nil))%nat with
| Some r => Some (rev r)
| None => None
end.
= Some (1%nat :: 2%nat :: 3%nat :: 4%nat :: nil) : option (list nat)
My first attempt included a nil
accumulator:
Lemma omap_tr_failed:
forall l res,
omap_tr f_tr l nil = Some res ->
omap f l = Some (rev res).
But I failed to do the induction. I assume it must be because the invariant is not strong enough to handle the general case.
Still, it seems to me that any of the following lemmas should be provable, but I'm afraid they are also not strong enough to enable the proof:
Lemma omap_tr':
forall l acc res,
omap_tr f_tr l acc = Some (res ++ acc) ->
omap f l = Some (rev res).
Lemma omap_tr'':
forall l acc res,
omap_tr f_tr l acc = Some res ->
exists res',
omap f l = Some res' /\
res = (rev res') ++ acc.
Should a standard double induction allow these lemmas to be proven directly, or do I need stronger invariants?