Coq:用'重写;forall';在假设或目标中
我已经在Coq中证明了多态列表上反向函数的“正确性”。下面的证明很好,但我有几个关于重写策略如何工作的问题 代码如下:Coq:用'重写;forall';在假设或目标中,coq,coq-tactic,Coq,Coq Tactic,我已经在Coq中证明了多态列表上反向函数的“正确性”。下面的证明很好,但我有几个关于重写策略如何工作的问题 代码如下: 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
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证明的归纳步骤中,如果我跳过介绍k,那么使用app_assoc重写会抱怨找不到匹配的子项
Found no subterm matching "(?M1058 ++ ?M1059) ++ ?M1060" in the current goal.
在这里,我想?在占位符名称之前,表示术语受到约束,在这种情况下,对于某些类型T,为列表T类型;由于目标中的rev l'和[h]是列表T的实例,因此我们可以预期目标中会出现匹配
另一方面,用归纳假设(rewrite->IHl')而不是app_assoc进行重写,之前不需要介绍
我发现这种重写行为有点令人困惑,Coq手册没有提供任何细节。我不想通读实现,但我需要对重写策略的功能有一个良好的操作理解,特别是关于术语匹配的工作原理。这方面的任何答案/参考都将受到高度赞赏。重写的复杂性在于有一个活页夹(所有k的
),它会使事情变得复杂。如果您只是想让事情正常运行,请使用setoid\u rewrite
而不是rewrite
,它将在活页夹下重写
rewrite IHl'
看起来像是在活页夹下发生的,但是重新编写的模式实际上并不涉及绑定变量,因此活页夹实际上并不重要。我的意思是:目标是
forall k : list T, (rev l' ++ [h]) ++ k = rev_acc l' (h :: k)
这与(即等于)相同:
我使用Ltac中的模式(rev l'+[h])
得到了它。现在很清楚,您可以重写应用到的零件,而忽略活页夹。当您执行重写IHl'
时,Coq很容易发现IHl
应该专门用于[h]
,然后重写继续进行
- 另一方面,
rewrite app_assoc
需要专用于三个列表,特别是rev l'
、[h]
和k
。无法在当前上下文中对其进行专门化,因为变量k
仅绑定在forall
下面。这就是为什么模式(?x++?y++?z
没有出现在目标中的原因
那么你实际上是做什么的呢?当然,您可以引入k
,这样就没有活页夹了,但有一种更简单、更通用的技术:Coq有一个通用的重写,可以在活页夹下重写,您可以通过调用setoid\u rewrite
(参见Coq参考手册中的内容)来使用它。手册告诉您需要声明态射,但在本例中,所有相关的态射都已为您在forall
中实现,因此setoid\u rewrite app\u assoc
将正常工作
请注意,虽然您总是可以为所有
引入一个来摆脱活页夹,但当您的目标是一个存在时,setoid\u rewrite
非常方便。您可以在活页夹下重写,而不用eexists
。您的问题很有趣!但是,既然它提供了一个软件基础练习的答案,你能删除它并发布一个没有提到这个练习的最低版本吗?我个人认为这个例子是从任何特定的SF练习中抽象出来的,细节对于揭示这些复杂情况很重要,这个问题比隐藏这个特定的解决方案更有价值。也许您可以删除问题本身中对软件基础的引用?据我所知,上述代码中练习的唯一解决方案是实现。我已经尽我所能将其最小化,同时仍然保持证据的上下文。如果需要,请随时编辑。
(fun l : list T => forall k : list T, l ++ k = rev_acc l' (h :: k)) (rev l' ++ [h])