software foundations LF Tactics

更多内容见 https://github.com/january147/software_foundations_notes

原始章节参考 software foundations LF Tactics

Tactics

apply

apply将某个假设或者定理应用于目标,如果目标同该假设或者定理完全一致(等式两端的内容也不能交换),则直接完成目标的证明;如果假设或者定理是->类型,并且目标匹配假设或者定理中->的右端,则尝试将目标转换为假设或者定理中->的左端。

例如下边这个定理的证明过程

Theorem silly_ex :
     (forall n, evenb n = true -> oddb (S n) = true) ->
     evenb 2 = true ->
     oddb 3 = true.
Proof.
  intros H1 H2. apply H1. apply H2.
Qed.

apply H1之前,目标如下,为oddb 3 = true

H1 : forall n : nat, evenb n = true -> oddb (S n) = true
H2 : evenb 2 = true
______________________________________(1/1)
oddb 3 = true

apply H1时,当前目标同H1不完全一致,但同H1->右边的部分匹配,因此目标被转换为左边的部分evenb 2 = true,如下所示

H1 : forall n : nat, evenb n = true -> oddb (S n) = true
H2 : evenb 2 = true
______________________________________(1/1)
evenb 2 = true

此时,目标同假设H2完全一致,使用apply H2即可完成证明。

类似于reflexivityapply也会自动对当前目标进行化简(展开)操作.

apply with

在coq apply一个定理时,可能出现其无法确定定理中某个变量和目标中具体变量的对应关系,这时候我们需要使用with子句为其指定定理中的某个general的变量对应的当前目标的具体变量.

general的变量是指该变量使用forall修饰,可以表示同类型的任何一个具体的变量。

例如对于如下定理trans_eq

Theorem trans_eq : ∀ (X:Type) (n m o : X),
  n = m → m = o → n = o.

我们将其应用于下边定理的的证明过程

Example trans_eq_example' : ∀ (a b c d e f : nat),
     [a;b] = [c;d] →
     [c;d] = [e;f] →
     [a;b] = [e;f].
Proof.
  intros a b c d e f eq1 eq2.
  (* 这一步的目标为[a;b] = [e;f], 对该目标应用上述定理trans_eq时coq无法确定定理中m应该和此处的哪个具体变量对应,
  	 因此我们需要使用with子句with m:=[c;d]来显式指定*)
  apply trans_eq with (m:=[c;d]).
  apply eq1. apply eq2. Qed.

transitivity

为目标引入一个中间值,根据等号的传递性转换为两个子目标。

目标 a = c

通过transitivity b可以分解为两个目标

子目标1 a = b
子目标2 b = c

symmetry

symmetry用于交换目标的等式两边的内容

Theorem rev_exercise1 : forall (l l' : list nat),
     l = rev l' ->
     l' = rev l.
Proof.
  intros l l' H.
  rewrite -> H.
  (* 此时目标为 l' = rev (rev l'), 由于rev_involutive定理的形式为rev (rev l) = l,我们使用symmetry交换
     目标等式的左右两边 *)
  symmetry.
  apply rev_involutive.
Qed.

injection

injection利用函数或者constructor的单射性质对假设中的等式进行转化。

单射性:f a = f b -> a = b

例如

Theorem double_injective_take2 : forall n m,
     double n = double m ->
     n = m.
Proof.
  intros n. induction n as [|n' Hn].
  -simpl. destruct m.
  --simpl. reflexivity.
  --simpl. discriminate.
  -destruct m.
  --discriminate.
  --intros H. f_equal.
  simpl in H. rewrite <- plus_n_Sm in H. rewrite <- plus_n_Sm in H.
  (* 此步骤时,假设H: S (S (n' + n')) = S (S (m + m))
  	 利用S的单射性,我们可以使用injection将假设H转换为n' + n' = m + m
  injection H. apply Hn.
Qed.

By writing [injection H as Hmn] at this point, we are asking Coq
to generate all equations that it can infer from [H] using the
injectivity of constructors (in the present example, the equation
[n = m]). Each such equation is added as a hypothesis (with the
name [Hmn] in this case) into the context.

injection生成多个新假设时,使用as为其指定名称不需要像destruct一样使用[], 例如injection H as H1 H2

discriminate

在证明->类型定理时,如果我们使用分类讨论,并且遇到某类情况->左端不可能成立的情况(即某个子目标对应的假设不可能成立),此时可以使用``discriminate`来移除这个情况下的子目标。

Theorem discriminate_ex1 : forall (n : nat),
  S n = O ->
  2 + 2 = 5.
Proof.
  intros n contra. 
  (* 假设S n = O不可能成立,因此使用discriminate即可完成该目标的证明 *)
  discriminate contra. 
Qed.

The [discriminate] tactic embodies this principle: It is used on a
hypothesis involving an equality between different
constructors (e.g., [S n = O]), and it solves the current goal
immediately. Here is an example:

f_equal

f_equal等价于apply H, 其中H

Theorem: forall (A B : Type) (f : A -> B) (x y : A),  x = y -> f x = f y
Theorem eq_implies_succ_equal : forall (n m : nat),    n = m -> S n = S m.Proof. intros n m H. apply f_equal. apply H. Qed.

unfold

有时候simpl不会展开所有的变量或者函数定义,例如

Definition square n := n * nLemma square_mult : forall n m, square (n * m) = square n * square m.Proof.  intros n m.  (* 执行simpl后,coq并没有展开square,即目标仍是 square (n * m) = square n * square m     因此我们无法继续进行证明 *)  simpl.    (* 此时我们需要使用unfold显式的指定展开suqare的定义,将目标转化为   	 n * m * (n * m) = n * n * (m * M) *)  unfold square.(** Now we have plenty to work with: both sides of the equality are    expressions involving multiplication, and we have lots of facts    about multiplication at our disposal.  In particular, we know that    it is commutative and associative, and from these it is not hard    to finish the proof. *)  rewrite mult_assoc.  assert (H : n * m * n = n * n * m).    { rewrite mult_comm. apply mult_assoc. }  rewrite H. rewrite mult_assoc. reflexivity.Qed.

destruct

前面的章节中已经介绍使用destruct对变量进行分类的讨论的方法,即destruct可以将变量按照其定义的形式展开,如果存在多种定义形式(使用多个不同constructor定义)则为每种形式生成一个子目标。除了普通的变量之外,destruct还可以对表达式的值进行展开和分类讨论,如下例子所示。

Theorem sillyfun_false : forall (n : nat),  sillyfun n = false.Proof.  intros n. unfold sillyfun.  (* 对表达式 n => 3的结果进行分类讨论*)  destruct (n =? 3) eqn:E1.    - (* n =? 3 = true *) reflexivity.    - (* n =? 3 = false *) destruct (n =? 5) eqn:E2.      + (* n =? 5 = true *) reflexivity.      + (* n =? 5 = false *) reflexivity.  Qed.

destruct中的eqn:用于引入一个关于分类讨论表达式的值得假设,即上述证明过程中的关于n =? 3n =? 5的值的假设。

(* 分类1 *)E1: n =? 3 = true(* 分类2 *)E1: n =? 3 = false(* 分类2.1 *)E2: n =? 5 = true(* 分类2.2 *)E2: n =? 5 = false

如果我们不使用eqn:子句,那么destruct仅对现有目标和假设中的对应表达式的值进行替换,而不会引入关于表达式值得假设。以上述证明为例,即仅把所有n =? 3替换成true或者false,而不引入n =? 3 = true或者n =? 3 = false的假设。这在某些证明中会阻碍证明过程,因为destruct仅替换了n =? 3,而不会替换其他的关于n的表达式(“例如n自身”),当destructn =? 3替换为true时如果不引入n =? 3 = true这样的假设,在destruct执行替换后我们将丢失n = 3这个信息。

在下述证明中,如果我们不使用eqn:子句,那么当destructn =? 3替换成true后,虽然oddb n = true是成立的,但我们已经丢失了n =? 3 = true这个假设,因此无法证明odd n = true。因此,在对复合表达式进行destruct时需要注意使用eqn:子句!。

Theorem sillyfun1_odd_FAILED : forall (n : nat),     sillyfun1 n = true ->     oddb n = true.Proof.  intros n eq. unfold sillyfun1 in eq.  destruct (n =? 3).  (* stuck... *)Abort.

generalize dependent

generalize dependent用于将intros引入的变量重新放回目标中,使其成为一个通用(general)的变量。

详细内容参考 调整intros的顺序小节

在假设条件中使用tactics

使用in子句可以让tactics对假设而不是证明目标进行操作。

Theorem silly3' : forall (n : nat),  (n =? 5 = true -> (S (S n)) =? 7 = true) ->  true = (n =? 5)  ->  true = ((S (S n)) =? 7).Proof.  intros n eq H.  symmetry in H. apply eq in H. symmetry in H.  apply H.  Qed.

注意我们对假设使用apply时(即使用假设H2对假设H1进行转化apply H2 in H1),需要保证H1同假设H2->左边的内容一致。

In other words, [apply L in H] gives us a form of “forward
reasoning”: from [X -> Y] and a hypothesis matching [X], it
produces a hypothesis matching [Y]. By contrast, [apply L] is
“backward reasoning”: it says that if we know [X -> Y] and we
are trying to prove [Y], it suffices to prove [X].

intros的使用时机

并不是所有定理的证明都要在一开始intros所有的变量和假设。因为intros一个变量(比如m),意味着这个变量被具体化,后续的证明过程中只有m本身能够和m匹配(m不再代表任何同类型的值了)。未引入变量m时,通常m可以表示同一类的任何一个变量。

例如对于如下命题

Theorem plus_n_n_injective : forall n m,     n + n = m + m ->     n = m.

未引入m时对n进行归纳(induction),生成的归纳假设为

forall m : nat, n' + n' = m + m -> n' = m

该假设中m可以匹配任意自然数。而引入m后对n进行归纳生成的归纳假设为

n' + n' = m + m -> n' =  m

此时m表示一个具体的值,而不再表示任何自然数,这个归纳假设相对上边的归纳假设来说过于特定,因此难以发挥作用。

### 调整intros的顺序

由于coq限制只能按照目标中定义的顺序引入变量和假设,我们如果需要先引入某个在目标中靠后的变量则无法仅使用intros完成。例如需要引入如下定理中的m而不引入n则无法仅通过intros完成。

Theorem double_injective_take2 : forall n m,     double n = double m ->     n = m.

为了实现上述操作,我们可以先将所有变量引入,然后使用generalize dependent

Theorem double_injective_take2 : forall n m,
     double n = double m ->
     n = m.
Proof.
  intros n m.
  (* [n] and [m] are both in the context *)
  generalize dependent n.
  (* Now [n] is back in the goal and we can do induction on
     [m] and get a sufficiently general IH. *)
  induction m as [| m' IHm'].
  - (* m = O *) simpl. intros n eq. destruct n as [| n'] eqn:E.
    + (* n = O *) reflexivity.
    + (* n = S n' *) discriminate eq.
  - (* m = S m' *) intros n eq. destruct n as [| n'] eqn:E.
    + (* n = O *) discriminate eq.
    + (* n = S n' *) apply f_equal.
      apply IHm'. injection eq as goal. apply goal. Qed.

The problem is that, to do induction on [m], we must first
introduce [n]. (And if we simply say [induction m] without
introducing anything first, Coq will automatically introduce [n]
for us!)

What can we do about this? One possibility is to rewrite the
statement of the lemma so that [m] is quantified before [n]. This
works, but it’s not nice: We don’t want to have to twist the
statements of lemmas to fit the needs of a particular strategy for
proving them! Rather we want to state them in the clearest and
most natural way.
What we can do instead is to first introduce all the quantified
variables and then re-generalize one or more of them,
selectively taking variables out of the context and putting them
back at the beginning of the goal. The [generalize dependent]
tactic does this.

重要练习

加法的单射性质

Theorem plus_n_n_injective : forall n m,     n + n = m + m ->     n = m.
Proof.
  intros n. induction n as [|n' Hn].
  -simpl. destruct m.
  --simpl. reflexivity.
  --simpl. discriminate.
  -destruct m.
  --discriminate.
  --intros H. f_equal.
  (* 假设H: S n' + S n' = S m + S m, 我们对其化简并应用plus_n_Sm 
  	 > plus_n_Sm: forall n m : nat, S (n + m) = n + S m
  	 从而得到S (S (n' + n')) = S (S (m + m))
  *)
  simpl in H. rewrite <- plus_n_Sm in H. rewrite <- plus_n_Sm in H.
  (* 利用S的单射性,使用injection将假设H转换为n' + n' = m + m,再应用归纳假设Hn
  	 即可完成证明*)
  injection H. apply Hn.
Qed.

combine and split

Theorem combine_split : forall X Y (l : list (X * Y)) l1 l2,  split l = (l1, l2) ->  combine l1 l2 = l.
Proof.
  (* 在对某个变量进行归纳证明前应该尽量少的引入其他变量,使得归纳假设本身尽可能的通用(general)
     而不是针对某个特定的变量*)
  intros X Y l. induction l as [|h t Hl].
  -intros l1 l2 H. injection H as H1 H2. rewrite <- H1. rewrite <- H2. reflexivity.
  (* 这里对split t表达式的结果进行了分类讨论,是十分重要的一步证明 *)
  -destruct h as [x y]. simpl. destruct (split t) as [lx ly].
  intros l1 l2 H. injection H as H3 H4. rewrite <- H3. rewrite <- H4. simpl.
  f_equal. apply Hl. reflexivity.
Qed.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值