更多内容见 https://github.com/january147/software_foundations_notes
原始章节参考 software foundations LF Poly
Polymorphism(多态)
某一些类型,比如list
, 其内部存储的元素的类型通常并不影响list
本身支持的一些操作(例如添加元素、删除元素、获取长度、获取某个元素等),因此list
的定义不应该和其内存存储的元素完全绑定(即不应该针对每种类型的元素都分别定义一个存储其元素的list
类型以及对应的行为)。list
应该作为一种比普通类型更高阶的类型,从而使得list
可以用于存储多种不同的类型,并且针对list
的操作应该可以应用到存储不同的类型数据的list
中,这样list
的操作就体现出了多态性(Polymorphism
), 即可以应用与多种不同的具体类型。
高阶类型list
按照如下方式定义,相比普通的类型,其多了一个类型参数,通过这个参数,可以存储不同类型具体数据的list
类型。
Inductive list (X:Type) : Type :=
| nil
| cons (x : X) (l : list X).
类似与将接受函数作为参数的函数称为高阶函数,通过其他具体类型构造的类型我将其称为高阶类型
What sort of thing is list itself? A good way to think about it is that the definition of list is a function from Types to Inductive definitions
The parameter X in the definition of list automatically becomes a parameter to the constructors nil and cons – that is, nil and cons are now polymorphic constructors; when we use them, we must now provide a first argument that is the type of the list they are building
隐式类型
定义时使用隐式类型
在某些情况下,我们在定义函数或者constructor时不为其参数变量指定类型,而由coq根据使用具体的定义来自动推导变量类型。
在如下定义中,coq根据后续的使用自动推导了X, x, count
三个变量的类型
Fixpoint repeat' X x count : list X :=
match count with
| 0 ⇒ nil X
| S count' ⇒ cons X x (repeat' X x count')
end.
调用时使用隐式类型
我们也可以在使用函数或者constructor时用填充符号_
来代替具体类型作为constructor的参数,由coq根据使用方式自动推导具体类型。例如如下例子使用_
代替了nat
.
Definition list123' :=
cons _ 1 (cons _ 2 (cons _ 3 (nil _))).
对于需要类型作为参数的函数或者constructor我们还可以使用Argument
命令来将这些参数设置为隐式参数(将隐式参数放到{}
中),让coq
始终自动推导类型,从而在使用这些函数和constructor时省略掉类型参数。
Arguments nil {X}.
Arguments cons {X} _ _.
Arguments repeat {X} x count.
使用Argument其实是重新声明了函数或者constructor的参数,我们也可以在定义函数(
Fixpoint, Definition
)时直接使用{}
来标识隐式参数. 另外,目前似乎只有类型参数可以是隐式参数。Definition fold_length {X : Type} (l : list X) : nat := fold (fun _ n => S n) l 0.
The [Arguments] directive specifies the name of the function (or constructor) and then lists its argument names, with curly braces around any arguments to be treated as implicit. (If some arguments of a definition don't have a name, as is often the case for constructors, they can be marked with a wildcard pattern [_].)
使用
Argument
定义了隐式参数后,在调用这些函数或者constructor时就不能再显示的传入这些参数
处理无法自动推导隐式类型的情况
通过Argument
定义了某些函数和constructor的隐式参数后,可能遇到coq无法自动推导隐式参数的情况.
Definition mynil : list nat := nil.
可以通过两种方法进行处理这个问题。
第一种是通过显示指定目标的类型,使得coq能够进行自动推导
Definition mynil : list nat := nil.
第二种是使用@
来暂时性地禁用隐式参数,从而显示的给出参数
Definition mynil' := @nil nat.
针对类型(Type)的Notation
Inductive prod (X Y : Type) : Type :=
| pair (x : X) (y : Y).
prod
是一个高阶类型,其接受两个类型作为参数从而构造出一个新的类型,可以使用Notation
来定义特定的方式用于调用prod
。由于如下定义使用了同自然数乘法mult
相同的Notation
定义,为了区分两个定义,这里使用: type_scope
来表示仅当参数为类型时*
才表示prod
,而普通值作为参数时*
表示mult
.
Notation "X * Y" := (prod X Y) : type_scope.
高阶函数
接收函数作为参数或者使用函数作为返回值的函数称为高阶函数. 常见的高阶函数包括了filter
, map
, flat_map
, fold
等等
filter
函数filter
接收一个函数test
,以及一个list
,其将list
中的每个元素作为参数分别执行test
,根据test
执行结果来保留其中一部分元素(test
执行结果为true
的元素)
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.
map
函数map
接收一个函数f
以及一个list
,将list
中的元素逐个使用f
进行转换。
输入 f [ a b c d e]
输出 [ f(a) f(b) f(c) f(d) f(e)]
Fixpoint map {X Y: Type} (f:X->Y) (l:list X) : (list Y) :=
match l with
| [] => []
| h :: t => (f h) :: (map f t)
end.
flat_map
函数flat_map
其也接收一个函数f
以及一个list
,将list
中的元素逐个使用f
进行转换,不过这里的函数f
接收单个元素并输出一个list
, flat_map
需要将函数f
输出的所有list
中的元素取出来放到一个list
中去。
假设 f(a) => [a1 a2 a3]
输入 f [ a b c d e]
输出 [ a1 a2 a3 b1 b2 b3 c1 c2 c3 d1 d2 d3 e1 e2 e3]
Fixpoint flat_map {X Y: Type} (f: X -> list Y) (l: list X)
: (list Y) :=
match l with
|nil => nil
|h :: t => (f h) ++ (flat_map f t)
end.
fold
函数fold
接收一个函数f
,一个list
以及一个初始值i
,其将list
中的元素作为函数f
的第一个参数传入,将上一个调用f
的结果作为f
的第二个参数传入,对list
中的元素进行逐个处理,并将最后一次调用f
得到的结果作为返回值返回。
输入 f [a b c] i
输出 f(c, f(b, f(a, i)))
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.
匿名函数
某些短小的,特定的函数可能不需要为其定义一个名称,此时可以使用匿名函数来定义, 匿名函数使用关键字fun
来定义
fun l => (length l) =? 1)
如下例子使用匿名函数作为调用高阶函数的参数
Example test_filter2':
filter (fun l => (length l) =? 1)
[ [1; 2]; [3]; [4]; [5;6;7]; []; [8] ]
= [ [3]; [4]; [8] ].
Proof. reflexivity. Qed.
command
Arguments
使用Argument
可以重新声明了函数或者constructor的参数,其主要功能(不确定)是将已定义函数或者constructor的类型参数改为隐式参数,以下例子使用{}
将constructor nil
, cons
以及函数repeat
中的类型参数X
声明为隐式参数,之后调用时将不能再显式传入这些参数。
Arguments nil {X}.
Arguments cons {X} _ _.
Arguments repeat {X} x count.
Fail
Fail
表示后续的语句将会发生错误,coq
在运行时确保Fail
后的语句确实发生了错误(没有发生错误则会发出警告),并且不会因为这些错误而停止执行。
Fail Definition mynil := nil.
The [Fail] qualifier that appears before [Definition] can be used with any command, and is used to ensure that that command
indeed fails when executed. If the command does fail, Coq prints the corresponding error message, but continues processing the rest of the file.
重要练习
定义fold_map
fold_map
即使用fold
函数来实现map
函数。我们知道fold
函数将list A
转换为B
,我们这里的fold_map
需要将list X
转换为list Y
,因此,要使用fold
来实现map
则fold
中的B
必须等于map
中的list Y
。因此我们传入fold
函数的函数如下
fun x (y : list Y) => (f x) :: y)
其第一个参数类型为X
,第二个参数类型是list Y
,执行的操作为将第一个参数x
传递给函数f
, 并将函数f
输出结果加入第二个参数y
(注意y
是一个list
)中. 并返回增加了元素f x
之后的y
.
使用fold
实现的map
以及正确性证明如下:
Definition fold_map {X Y: Type} (f: X -> Y) (l: list X) : list Y := fold (fun x (y : list Y) => (f x) :: y) l nil.
Theorem fold_map_correct: forall (X Y : Type) (f: X->Y) (l: list X), fold_map f l = map f l.
Proof.
intros X Y f l. induction l as [|h t Hl].
-simpl. reflexivity.
-simpl. rewrite <- Hl. reflexivity.
Qed.
基于高阶函数定义的自然数的乘方运算
将函数n f
表示一个将f
嵌套执行n次的函数,函数m n
则表示一个将函数n
嵌套执行m
次的函数。我们知道n f
表示将f
嵌套执行n
次,那么 n (n f)
则表示将f嵌套执行n * n
次,n (n (n f))
则表示将f
嵌套执行n * n * n
次。因此,将函数n
嵌套执行m
次得到的函数作用于f
则会得到一个将f
执行n * n * n * n...
共计m
个n
相乘的结果次的函数,这就是我们所需的exp
(乘方运算)的定义.
Definition exp (n m : cnat) : cnat := fun (X : Type) (f : X -> X) (x : X) => (m (X -> X) (n X) f) x.