【Coq 笔记自用】

本文介绍了在Coq编程语言中,如何使用`destruct`和`induction`进行数据结构分解和归纳证明,以及`exact`和`refine`在提供精确证明和调整目标类型中的作用。还讨论了Set和Prop类型的区别,以及Curry-Howard对应关系在证明过程中的应用。
摘要由CSDN通过智能技术生成

Coq随记
本文不做教学,只是我在学习使用中遇到的知识点,需要学习的可以搜索《Software Foundations》,以及一个汉化的网址https://coq-zh.github.io/SF-zh/

一些教学网址

https://www.cnblogs.com/chesium/p/coq-tutorial.html

两种方式定义函数

第一种方式使用未知参数,c是参数,next_col_alt是函数名
第二种方式是使用函数构造函数,将函数作为其它函数的结果’返回’

Definition next_col_alt c : tlcolor :=
    match c with
    | Green  => Orange
    | Orange => Red
    | Red    => Green
    end.
Definition next_col_old: tlcolor -> tlcolor :=
  fun c =>
    match c with 
    | Green  => Orange
    | Orange => Red
    | Red    => Green
    end.

Set 和 Prop两种type

  • definition的类型是Set
  • Theorem的类型是Prop
    Set->Prop

Curry-Howard correspondence :

                                  proof   =   functional program
                                formula   =   type
       provide a proof p of a formula T   =   write a program
 checking that p is indeed a proof of T   =   type checking

依赖类型

常常与返回type的程序结合
##归纳类型定义如下,不使用模式匹配的语法,直接列出构造子及其参数

Inductive list (A : Set) : Set :=
| Nil : list A
| Cons : A -> list A -> list A.

归纳定义的命题

通过如下规则来建立 n 是偶数的性质:
规则 ev_0: 0 是偶数。 规则 ev_SS: 如果 n 是偶数, 那么 S (S n) 也是偶数。
要证明 4 是偶数的方法:
根据规则 ev_SS,需要证明 2 是偶数。这时,只要证明 0 是偶数, 我们可继续通过规则 ev_SS 确保它成立。而使用规则 ev_0 可直接证明 0 是偶数。

Inductive ev : nat -> Prop := (*定义了一个函数,从nat到Prop*)
| ev_0 : ev 0
| ev_SS (n : nat) (H : ev n) : ev (S (S n)).

注意,nat参数出现在冒号 右侧,这允许在不同的构造子类型中使用不同的值。每个构造子的类型 在冒号后显式指定,必须有ev n的形式。
ev 定义了一个性质 ev : nat → Prop,其包括 “证据构造子” ev_0 : ev 0 和 ev_SS : ∀ n, ev n → ev (S (S n))。“证据构造子” 等同于已经证明过的定理。可食用apply证明某个数的ev性质。

归纳关系:我们可以认为被一个数所参数化的命题(比如 ev)是一个’性质’,也即, 它定义了 nat 的一个子集,其中的数可以被证明满足此命题。 以同样的方式,我们可认为有两个参数的命题是一个’关系’,也即,它定义了一个 可满足此命题的序对集合。

一些内置函数 或者 lemma

negb_true_iff: forall b : bool, negb b = true <-> b = false

Tactics [refine], [intro] [apply] and [revert]

- [intro x]
  is the same as
  [refine (fun x => _)]

- [revert x] is the converse of [intro x];
  it is essentially the same as
  [refine (_ x); clear x].
  The effect of [clear x] is to remove [x] from the visible environment
  (its scope is closed).

- [apply f]
  is the same as
  [refine f], or
  [refine (f _)], or
  [refine (f _ _)], etc.
  according to the type of [f].

  Example
<<
  f : T1 -> T2
  ============
  T2
>>
  Here we can use [refine (f _)], then we still have to find something of type [T1].

  - [destruct E as [ (* Cons1 *) x | (* Cons2 *) | (* Cons3 *) y z] )
  - [destruct E as [ x |  |  y z] )
  - [destruct E as [  |  | ] )  (* NOT RECOMMENDED! *)
    is the same as
<<
    refine (match E with
             | Cons1 x => _
             | Cons2 => _
             | Cons3 y z => _
             end).
    refine (match E with
             | Cons2 => _
             | _ => _default
             end).

>>
    where the type of [E] is an inductive type with constructors [Cons1], [Cons2] and [Cons3] having respectively 1, 0 and 2 arguments.

intros:将前提/变量从证明目标移到上下文中
reflexivity:(当目标形如 e = e 时)结束证明
apply:用前提、引理或构造子证明目标
apply… in H:将前提、引理或构造子应用到上下文中的假设上(正向推理)
apply… with…:为无法被模式匹配确定的变量显式指定值
simpl:化简目标中的计算
simpl in H:化简前提中的计算
rewrite:使用相等关系假设(或引理)来改写目标
rewrite … in H:使用相等关系假设(或引理)来改写前提
symmetry:将形如 t=u 的目标改为 u=t
symmetry in H:将形如 t=u 的前提改为 u=t
unfold:用目标中的右式替换定义的常量
unfold… in H:用前提中的右式替换定义的常量
destruct… as…:对归纳定义类型的值进行情况分析
destruct… eqn:…:为添加到上下文中的等式指定名字, 记录情况分析的结果
induction… as…: 对归纳定义类型的值进行归纳
injection: 通过归纳定义类型的值之间相等的注入性进行推理
discriminate: 根据归纳定义类型的值之间相等性的构造函数的不相交性进行推理。处理归纳类型中构造子(constructor)相等性的策略。
contradiction: 用于解决证明中出现的矛盾。当你在证明过程中得到一个假命题(例如,通过假设某个命题既是真的又是假的),你可以使用 contradiction 来证明原始目标。这个策略通常在你已经证明了某个假设导致了逻辑上的矛盾时使用。

Theorem example_theorem : forall (P : Prop), (P /\ ~P) -> False.
Proof.
intros P H.
apply H.
contradiction H.

contradiction 会尝试找到一个假设的否定形式,并用它来证明原始假设,从而完成证明。
assert (H: e)(或 assert (e) as H):引入“局部引理”e 并称之为 H
generalize dependent x:将变量 x(以及任何依赖它的东西) 从上下文中移回目标公式内的前提中,如假设中x: nat ,移到前提中是 forall x,… -> …
clear H:从上下文中删除前提 H。
subst x:对于变量 x,在上下文中查找假设 x = e 或 e = x, 将整个上下文和当前目标中的所有 x 替换为 e 并清除该假设。
subst:替换掉’所有’形如 x = e 或 e = x 的假设(其中 x 为变量)。
rename… into…:更改证明上下文中前提的名字。例如, 如果上下文中包含名为 x 的变量,那么 rename x into y 就会将所有出现的 x 重命名为 y。
assumption:尝试在上下文中查找完全匹配目标的前提 H。 如果找到了,那么其行为与 apply H 相同。
contradiction:尝试在当前上下文中查找逻辑等价于 False 的前提 H。 如果找到了,就解决该目标。
constructor:尝试在当前环境中的 Inductive 定义中查找可用于解决当前目标的构造子 c。如果找到了,那么其行为与 apply c 相同。

[ destruct ]和[ induction ]

  • destruct类似模式匹配把一个复杂的数据类型分解成子类型。他适用于模式匹配和分解数据结构的策略。当你有一个归纳类型(inductive type)或者一个包含构造子(constructors)的数据结构时,你可以使用destruct来“分解”一个表达式,将其转换成它的组成部分。
Inductive tlcolor : Set :=
  | Green  : tlcolor
  | Orange : tlcolor
  | Red    : tlcolor
.
Definition next_col : tlcolor -> tlcolor :=
  fun c =>
    match c with
    | Green  => Orange
    | Orange => Red
    | Red    => Green
    end.
Lemma nextnextnext_id : forall c : tlcolor, next_col (next_col (next_col c)) = c.
Proof.
(** PLEASE COMPLETE HERE *)
  intro c0. destruct c0 as [| |].
  + cbn [next_col]. reflexivity.
  + cbn [next_col]. reflexivity.
  + cbn [next_col]. reflexivity.
Qed.

这里destruct和induction都对

  • induction归纳,用于遍历所有的情况,依赖于归纳假设来证明一个属性对于整个归纳类型都成立。
  • 要证明P(n)对所有n都成立,分两步证明:一证明P(0) 成立,二证明P(n’)->P(S n’)成立。会引入新的假设,P(n’)成立。
Fixpoint revt t : bintree :=
  match t with
  | L c   => L c
  | N l r => N (revt r) (revt l)
  end.
 Theorem revt_revt : forall t, revt (revt t) = t.
Proof.
  intro t.
  induction t as [|t1 IH_t1 t2 IH_t2]. (*只能用induction*)
  - cbn [revt]. reflexivity.
  - cbn [revt]. rewrite IH_t1. rewrite IH_t2. reflexivity.
Qed.

[ exact ]和[ refine ]

  • exact(精确匹配),确保某个特定的定理或等式被完全应用,并且所有的变量都匹配时可用
  • refine(精细调整),允许当前目标与提供的定理或等式不完全匹配,可留下hole(joker)

Tacticals 泛策略,表示一个接受其它策略作为参数的策略,可称为 高阶策略

  • try T : 如果T策略失败,则啥也不做,成功的话,和T一样
  • ; :表示复合策略T; T’,先执行T,然后在T生成的每个“子目标”中执行T’,常与 try一同配合使用
  • T; [T1 | T2 | … | Tn] :首先执行 T,然后在 T 生成的第一个字母表中执行 T1, 在第二个子目标中执行 T2,以此类推
  • repeat 泛策略接受另一个测略并重复应用它直至失败,必须使用 ( )标记要重复应用的策略
  • omega策略处理一阶逻辑。 在 Coq 中,omega 策略已经被弃用。它被新的策略如 lia(线性算术)和 nia(非线性算术)所取代。如果你在旧的代码中看到 omega,可以用 lia 来替代
    如果证明目标是由以下元素构成的式子:
    数值常量、加法(+ 和 S)、减法(- 和 pred)以及常量乘法 (这就是 Presburger 算术的构成要素)
    相等关系(= 和 ≠)和序(≤)
    逻辑连结 ∧、∨、¬ 和 →
    那么调用 omega 要么会解决该证明目标,要么就会失败,这意味着该目标为假

公式F可视为type,证明过程p视为数据或者程序

In the famous Curry-Howard correspondence, “[p] is a proof of formula [F]” can be written [p : F],
that is, [p] can be seen as a data or a functional program of type [F]. In the sequel we write “function” for as a shorthand for “functional program”.
[Theorem t : forall x : some_type, blablah x.]
And consider an expression [e] of type [some_type], then [t] applied to [e], that is [t e] makes sense and yields a proof of [blablah e].

Lemma nextnextnext_id : forall c, next_col (next_col (next_col c)) = c.
Proof.
  intro c.
  destruct c as [ (*Green*) | (*Orange*) | (*Red*) ]; reflexivity.
Qed.

Example ex_nnnid : next_col (next_col (next_col (next_col Green))) = next_col Green.
Proof. (* without computation such as [cbn] *)
  (* We want to use the previous theorem, with [next_col Green] of [c] *)
  Check (nextnextnext_id (next_col Green)). (* right on target! *)
  exact (nextnextnext_id (next_col Green)).
Qed.

Option可选类型

存在返回一个错误值的可能

Inductive natoption : Type :=
  | Some (n : nat)
  | None.

Some 2的类型是natoption

多态 和隐式参数、显示参数

任意类型X (X: Type) ,将 cons 构造子的 nat 参数换成了任意的类型 X,函数头的第一行添加了 X 的绑定, 而构造子类型中的 natlist 则换成了 list X。

Inductive natlist : Type :=
  | nil
  | cons (n : nat) (l : natlist).
  
Inductive list (X:Type) : Type :=
  | nil
  | cons (x : X) (l : list X).
  (*隐式参数是花括号里的X,其他参数可用_ _替代,nil没有参数,cons有两个参数x和l,也可用他们的名字  Arguments cons {X} x l. *)
Arguments nil {X}.
Arguments cons {X} _ _.

在定义函数时就声明隐式参数。而在Inductive 构造子中使用显式的 Argument 声明,可以使用list nat、list bool,若写成Inductive list’ {X:Type} : Type :=则类型会变成隐式的,只能写list
可以在函数名前加上前缀 @ 来强制将隐式参数变成显式的:

Check @nil : ∀ X : Type, list X.
Definition mynil' := @nil nat.

高阶函数

  1. 过滤器:参数为一个 X 的谓词(即一个从 X 到 bool 的函数)和 一个元素类型为 X 的列表,返回一个新的list X列表,筛选出满足X的元素
Fixpoint filter {X:Type} (test: X -> bool) (l:list X) : (list X) :=
  match l with
  | [][]
  | h :: t ⇒ if test h then h :: (filter test t)
                        else filter test t
  end.

匿名函数:一次性的函数,不需要取名字 (fun n => n*n),这个函数表示输入一个n返回n的平方
2. 映射 map :参数为一个函数f: X -> Y 和一个列表,返回一个新类型的列表

Fixpoint map {X Y: Type} (f:X-> Y) (l:list X) : (list Y) :=
  match l with
  | [][]
  | h :: t ⇒ (f h) :: (map f t)
  end.

  1. 折叠 fold:参数为一个函数f,输入X类型和Y类型的参数,返回Y类型的参数;一个listX;b是f初始的第二个输入。fold将f插入到给定列表的每一对元素之间: fold plus [1;2;3;4] 0为求和0 1 2 3 4;fold mult [1;2;3;4] 1 = 24.
Fixpoint fold {X Y: Type} (f: X->Y->Y) (l: list X) (b: Y) : Y :=
  match l with
  | nil ⇒ b
  | h :: t ⇒ f h (fold f t b)
  end.
  1. 多参函数 将函数作为数据传入

Check plus : nat → nat → nat.
该表达式中的每个 → 实际上都是一个类型上的’二元’操作符。 该操作符是’右结合’的,因此 plus 的类型其实是 nat → (nat → nat) 的简写,即,它可以读作“plus 是一个单参数函数,它接受一个 nat 并返回另一个函数,该函数接受另一个 nat 并返回一个 nat”。

逻辑系统

命题的类型Prop,'是’一个命题与该命题’可以证明’是两回事。'所有’语法形式良好的命题,无论是否为真,其类型均为 Prop。
参数化’的命题 – 也就是一个接受某些类型的参数, 然后返回一个命题的函数:
例如,以下函数接受某个数字,返回一个命题断言该数字等于 3:

Definition is_three (n : nat) : Prop :=  n = 3.
Check is_three : nat → Prop.

返回命题的函数可以说是定义了其参数的’性质’。

相等关系运算符 = 也是一个返回 Prop 的函数。
表达式 n = m 只是 eq n m 的语法糖(它使用 Notation 机制定义在 Coq 标准库中)。由于 eq 可被用于任何类型的元素,因此它也是多态的:

Check @eq : ∀ A : Type, A → A → Prop.

逻辑联结词 /\ 链接的是断言,中缀记法 只是 and A B 的语法糖, Check and : Prop → Prop → Prop.

证明合取的命题通常使用 split 策略。要反过来使用,即’使用’合取前提来帮助证明时, 我们会采用 destruct 策略。
如果当前证明上下文中存在形如 A ∧ B 的前提 H,那么 destruct H as [HA HB] 将会从上下文中移除 H 并增加 HA 和 HB 两个新的前提,前者断言 A 为真,而后者断言 B 为真。

析取 / : or : Prop → Prop → Prop

True、False是命题; true false是bool类型,使用||运算
True有一个证明是 I

Lemma True_is_true : True.
Proof. apply I. Qed.

逻辑等价

Definition iff (P Q : Prop) := (P → Q)(Q → P).
Notation "P <-> Q" := (iff P Q)
                      (at level 95, no associativity)
                      : type_scope.

广集与逻辑等价
Coq 的某些策略会特殊对待 iff 语句,以此来避免操作某些底层的证明状态。 特别来说,rewrite 和 reflexivity 不仅可以用于相等关系,还可用于 iff 语句。为了开启此行为,我们需要导入 Coq 库来支持它:

From Coq Require Import Setoids.Setoid.

“广集(Setoid)”指配备了等价关系的集合,即满足自反性、对称性和传递性的关系。 当一个集合中的两个元素在这种关系上等价时,可以用 rewrite 将其中一个元素替换为另一个。我们已经在 Coq 中见过相等性关系 = 了: 当 x = y 时,我们可以用 rewrite 将 x 替换为 y,反之亦可。
同样,逻辑等价关系 <-> 也满足自反性、对称性和传递性, 因此我们可以用它将替换命题中的一部分替换为另一部分:若 P <-> Q,那么我们可以用 rewrite 将 P 替换为 Q,反之亦可。

exist

使用命题编程

断言: 元素 x 出现在列表 l 中

Fixpoint In {A : Type} (x : A) (l : list A) : Prop :=
  match l with
  | [] ⇒ False
  | x' :: l' ⇒ x' = x ∨ In x l'
  end.

***

IMP

算术和布尔表达式求值有两种定义方式:函数式和关系式。在大型的 Coq 开发中,经常可以看到一个定义同时给出了函数式和关系式’两种’风格, 加上一条陈述了二者等价的引理,以便在之后的证明中能够在这两种视角下随意切换。

  • 函数式:
    -函数的定义是确定性的,且在所有参数上定义;可以利用 Coq 的计算机制在证明过程中简化表达式:unfold、cbn、cbv等。
    -函数还可以直接从 Gallina“提取”出 OCaml 或 Haskell 的可执行代码
  • 关系式:
    -定义的东西不太容易用函数表达,或者确实’不是’函数的情况
    -更优雅,更容易理解。Coq 会根据 Inductive 定义自动生成不错的反演函数和归纳法则
Inductive aexp : Type :=
  | ANum (n : nat)
  | APlus (a1 a2 : aexp)
  | AMinus (a1 a2 : aexp)
  | AMult (a1 a2 : aexp).
  
Fixpoint aeval (a : aexp) : nat := (*函数式*)
  match a with
  | ANum n => n
  | APlus a1 a2 => (aeval a1) + (aeval a2)
  | AMinus a1 a2 => (aeval a1) - (aeval a2)
  | AMult a1 a2 => (aeval a1) × (aeval a2)
  end.
Inductive aevalR : aexp -> nat -> Prop :=(*关系式*)
  | E_ANum n :
      aevalR (ANum n) n
  | E_APlus (e1 e2: aexp) (n1 n2: nat) :
      aevalR e1 n1 ->
      aevalR e2 n2 ->
      aevalR (APlus e1 e2) (n1 + n2)
  | E_AMinus (e1 e2: aexp) (n1 n2: nat) :
      aevalR e1 n1 ->
      aevalR e2 n2 ->
      aevalR (AMinus e1 e2) (n1 - n2)
  | E_AMult (e1 e2: aexp) (n1 n2: nat) :
      aevalR e1 n1 ->
      aevalR e2 n2 ->
      aevalR (AMult e1 e2) (n1 × n2).

错误思想 注意

下面这个命题 永远都返回真命题,这样不能完成bool到Prop的转换

Definition is_true (b : bool) : Prop := 
  match b with 
  |true => (b = true)
  |false => (b=false)
  end.

coq里有专门的bool和prop的转换,reflect 是一个用于将逻辑命题(Prop 类型)与布尔值(bool 类型)联系起来的关键字。它允许你将一个布尔值提升为一个逻辑命题,并在证明中使用这个命题。

reflect 的典型用法是将一个布尔表达式的结果转换为一个逻辑命题,这个命题可以被用作证明中的一个步骤。当你使用 reflect 时,你实际上是在声明某个布尔值反映了某个逻辑命题的真假。

这里是 reflect 的基本语法:

reflects P b.

P 是一个逻辑命题(Prop 类型)。
b 是一个布尔值(bool 类型),它表示 P 的真假。

  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值