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.
高阶函数
- 过滤器:参数为一个 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.
- 折叠 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.
- 多参函数 将函数作为数据传入
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 的真假。