如果simpl不能减少所有必要步骤,该怎么办?

时间:2019-04-27 10:00:46

标签: coq

以下示例摘自Software Foundations一书的Poly一章。

Definition fold_length {X : Type} (l : list X) : nat :=
  fold (fun _ n => S n) l 0.

Theorem fold_length_correct : forall X (l : list X),
  fold_length l = length l.
Proof.
intros.
induction l.
- simpl. reflexivity.
- simpl.
1 subgoal
X : Type
x : X
l : list X
IHl : fold_length l = length l
______________________________________(1/1)
fold_length (x :: l) = S (length l)

我希望它可以简化左侧的步骤。当然应该能够。

Theorem fold_length_correct : forall X (l : list X),
  fold_length l = length l.
Proof.
intros.
induction l.
- simpl. reflexivity.
- simpl. rewrite <- IHl. simpl.
1 subgoal
X : Type
x : X
l : list X
IHl : fold_length l = length l
______________________________________(1/1)
fold_length (x :: l) = S (fold_length l)

在运行测试期间,我遇到一个问题,simpl拒绝潜水,但是reflexivity做到了,所以我在这里尝试了同样的事情,证明成功了。

请注意,在给出目标状态的情况下,人们不会期望自反性会通过,但确实如此。在此示例中,它起作用了,但确实迫使我按照与原先相反的方向进行了重写。

是否可以对simpl进行更多控制,以实现所需的减少量?

1 个答案:

答案 0 :(得分:3)

出于这个答案的目的,我假设fold的定义与

相似
Fixpoint fold {A B: Type} (f: A -> B -> B) (u: list A) (b: B): B :=
match u with
| [] => b
| x :: v => f x (fold f v b)
end.

(基本上是标准库中的fold_right)。如果您的定义大不相同,那么我建议的策略可能行不通。


这里的问题是simpl的行为,其中常量必须先展开,然后才能简化。来自the documentation

  

请注意,只有简单名称可以在递归调用中重用的透明常量才能通过simpl展开。例如,由plus':= plus定义的常量可能会展开并在递归调用中重用,但是诸如succ:= plus(S O)之类的常量永远不会展开。

这有点难以理解,所以让我们举个例子。

Definition add_5 (n: nat) := n + 5.

Goal forall n: nat, add_5 (S n) = S (add_5 n).
Proof.
  intro n.
  simpl.
  unfold add_5; simpl.
  exact eq_refl.
Qed.

即使将simpl简化为add_5 (S n),您也会看到对S (n + 5)的第一次调用没有任何作用。但是,如果我先unfold add_5,它会完美地工作。我认为问题在于plus_5不直接是Fixpoint。尽管plus_5 (S n)等效于S (plus_5 n),但实际上并不是它的定义。因此,Coq无法识别其“名称可以在递归调用中重用”。 Nat.add(即“ +”)被直接定义为递归Fixpoint,因此simpl确实简化了它。

simpl的行为可以稍作更改(再次参见文档)。正如Anton在评论中提到的那样,当Arguments试图简化时,您可以使用simpl白话命令来更改。 Arguments fold_length _ _ /.告诉Coq,如果提供了至少两个参数,则fold_length应该展开(斜杠在左侧的必需参数和右侧的不必要参数之间分隔)。[sup] 1 [\ sup ]

cbn是您不想使用的一种更简单的策略,它默认情况下在此处工作,并且总体上效果更好。引用the documentation

  

cbn策略被认为是一种更简单,更快捷,更可预测的替代方案。

simpl带有Arguments和斜杠,也没有cbn都无法将目标降低到您想要的目标,因为它将展开fold_length但不会重新折叠。您可能会认识到对fold的呼叫只是fold_length l,然后用fold (fold_length l)重新折叠。

您遇到的另一种可能性是使用change策略。似乎您已经知道fold_length (a :: l)应该简化为S (fold_length l)。在这种情况下,您可以使用change (fold_length (a :: l)) with (S (fold_length l)).,Coq会尝试将一个转换为另一个(仅使用基本转换规则,而不是像rewrite这样的等式)。

使用以上两种策略中的任意一种达到S (fold_length l) = S (length l)的目标之后,就可以根据需要使用rewrite -> IHl.


  1. 我认为斜线只会使simpl展开的内容更少,这就是为什么我之前没有提到它。我不确定实际的默认值是什么,因为将斜线放在任意位置似乎会使simpl展开fold_length