我已证明Coq中多态 Lists 上反向函数的“正确性”。以下证明很好用,但是我对 rewrite 策略的工作方式有一些疑问。
代码如下:
Require Export Coq.Lists.List.
Import ListNotations.
Fixpoint rev {T:Type} (l:list T) : list T :=
match l with
| nil => nil
| h :: t => rev t ++ [h]
end.
(* Prove rev_acc equal to above naive implementation. *)
Fixpoint rev_acc {T:Type} (l acc:list T) : list T :=
match l with
| nil => acc
| h :: t => rev_acc t (h::acc)
end.
Theorem app_assoc : forall (T:Type) (l1 l2 l3 : list T),
(l1 ++ l2) ++ l3 = l1 ++ (l2 ++ l3).
Proof.
Admitted.
Theorem rev_acc_correct : forall (T:Type) (l k :list T),
rev l ++ k = rev_acc l k.
Proof.
intros T l.
induction l as [ | h l' IHl' ].
- reflexivity.
- simpl.
intro k.
(* Why is "intro k" required for "rewrite -> app_assoc" *)
(* But "rewrite -> IHl'" works regardless of "intro k". *)
(* generalize (rev l'), [h], k. *)
rewrite -> app_assoc.
simpl.
rewrite -> IHl'.
reflexivity.
Qed.
在 rev_acc_correct 证明的归纳步骤中,如果我跳过前奏,则用 app_assoc 重写会抱怨找不到匹配项子项。
Found no subterm matching "(?M1058 ++ ?M1059) ++ ?M1060" in the current goal.
在这里,我假设占位符名称前的?表示该术语受约束,在这种情况下,某些类型的类型为 List T T ;并且由于目标中的 rev l'和 [h] 是 List T 的实例,因此人们期望目标匹配。>
另一方面,使用归纳假设( rewrite-> IHl')代替 app_assoc 进行重写,而无需前导k 之前。
我发现 rewrite 的这种行为有点令人困惑,并且Coq手册未提供任何详细信息。我不想通读实现,但是我需要对重写策略的作用有很好的操作理解,尤其是在术语匹配如何工作方面。对此方向的任何回答/推荐都将受到高度赞赏。
答案 0 :(得分:5)
此重写的复杂之处在于,存在一个 binder (forall k
),它会使事情复杂化。如果您只是想让一切正常,请使用setoid_rewrite
而不是rewrite
,它将在活页夹下重写。
rewrite IHl'
看起来像是在绑定器下发生的,但是要重写的模式实际上并不涉及绑定变量,因此绑定器实际上并不重要。这就是我的意思:目标是
forall k : list T, (rev l' ++ [h]) ++ k = rev_acc l' (h :: k)
与(等于)相同:
(fun l : list T => forall k : list T, l ++ k = rev_acc l' (h :: k)) (rev l' ++ [h])
我在Ltac中使用pattern (rev l' ++ [h])
获得的。现在很明显,您可以只重写要应用的零件,而忽略活页夹。当您执行rewrite IHl'
时,Coq会很容易地发现IHl
应该专门用于[h]
,并且重写会继续进行。
rewrite app_assoc
需要专门用于三个列表,特别是rev l'
,[h]
和k
。它不能在当前上下文中专用,因为变量k
仅绑定在forall
下。这就是为什么模式(?x ++ ?y) ++ ?z
没有出现在目标中的原因。那么您实际上在做什么?您当然可以引入k
,所以没有活页夹,但是有一种更简单,更通用的技术:Coq具有广义的重写功能,可以在活页夹下进行重写,您可以通过调用setoid_rewrite
来使用(请参见{ Coq参考手册中的{3}}。手册告诉您需要声明态射,但是在这种情况下,forall
都已为您实现了相关的射态,因此setoid_rewrite app_assoc
就可以使用。
请注意,尽管您总是可以引入forall
来摆脱活页夹,但是当您的目标是setoid_rewrite
时,exists
会非常方便。不必使用eexists
,而只需在活页夹下重写即可。