以下示例摘自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
进行更多控制,以实现所需的减少量?
答案 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.
。
simpl
展开的内容更少,这就是为什么我之前没有提到它。我不确定实际的默认值是什么,因为将斜线放在任意位置似乎会使simpl
展开fold_length
。