Coq:在假设或目标中用“ forall”重写

时间:2018-10-02 05:35:08

标签: coq coq-tactic

我已证明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手册未提供任何详细信息。我不想通读实现,但是我需要对重写策略的作用有很好的操作理解,尤其是在术语匹配如何工作方面。对此方向的任何回答/推荐都将受到高度赞赏。

1 个答案:

答案 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,而只需在活页夹下重写即可。