Poly多态与高阶函数
多态列表
...不过这样很快就会变得乏味。
部分原因在于我们必须为每种数据类型都定义不同的构造子,
然而主因还是我们必须为每种数据类型再重新定义一遍所有的列表处理函数
( length、rev 等)以及它们所有的性质(rev_length、app_assoc 等)。
为避免这些重复,Coq 支持定义'多态'归纳类型。
例如,以下就是'多态列表'数据类型。
这和上一章中 natlist 的定义基本一样,只是将 cons 构造子的
nat 参数换成了任意的类型 X,函数头的第一行添加了 X 的绑定,
而构造子类型中的 natlist 则换成了 list X。(我们可以重用构造子名
nil 和 cons,因为之前定义的 natlist 在当前作用之外的一个 Module 中。)
list 本身又是什么类型?一种不错的思路就是把 list 当做从 Type
类型到 Inductive 归纳定义的'函数';或者换种更简明的思路,即 list
是个从 Type 类型到 Type 类型的函数。对于任何特定的类型 X,
类型 list X 是一个 Inductive 归纳定义的,元素类型为 X 的列表的集合。
list 的定义中的参数 X 自动
成为构造子 nil 和 cons 的参数 —— 也就是说,nil 和 cons 在这里是多态
的构造子;现在我们调用它们的时候必须要提供一个参数,就是它们要构造的列表的具
体类型。例如,nil nat 构造的是 nat 类型的空列表。
cons nat 与此类似,它将类型为 nat 的元素添加到类型为
list nat 的列表中。以下示例构造了一个只包含自然数 3 的列表:
nil 的类型会是什么呢?也许我们可以(根据定义)说它是 list X,
不过这样它就不是接受 X 返回 list 的函数了。再提出一种:Type → list X
并没有解释 X 是什么,(X : Type) → list X 则比较接近。
Coq 对这种情况的记法为 ∀ X : Type, list X:
类似地,定义中 cons 的类型看起来像 X → list X → list X
然而以上述约定来解释 X 的含义则可以得到类型
∀ X, X → list X → list X。
(关于记法的附注:在 .v 文件中,量词“forall”会写成字母的形式,
而在生成的 HTML 和一些设置了显示控制的 IDE 中,∀
通常会渲染成一般的“倒 A”数学符号,虽然你偶尔还是会看到英文拼写的
“forall”。这只是排版上的效果,它们的含义没有任何区别。)
如果在每次使用列表构造子时,都要为它提供类型参数,那样会很麻烦。
不过我们很快就会看到如何省去这种麻烦。
现在我们可以回过头来定义之前写下的列表处理函数的多态版本了。
例如 repeat:
Fixpoint repeat (X : Type) (x : X) (count : nat) : list X :=
match count with
| 0 ⇒ nil X
| S count' ⇒ cons X x (repeat X x count')
end.
同 nil 与 cons 一样,我们可以通过将 repeat
应用到一个类型、一个该类型的元素以及一个数字来使用它:
要用 repeat 构造其它种类的列表,
我们只需通过对应类型的参数将它实例化即可:
Module MumbleGrumble.
Inductive mumble : Type :=
| a
| b (x : mumble) (y : nat)
| c.
Inductive grumble (X:Type) : Type :=
| d (m : mumble)
| e (x : X).
对于某个类型 X,以下哪些是 grumble X 良定义的元素?
(在各选项后填“是”或“否”。)
- d (b a 5)
- d mumble (b a 5)
- d bool (b a 5)
- e bool true
- e mumble (b c 0)
- e bool (b c 0)
- c
(* 请在此处解答 *)
End MumbleGrumble.
(* 请勿修改下面这一行: *)
Definition manual_grade_for_mumble_grumble : option (nat×string) := None.
☐
End MumbleGrumble.
(* 请勿修改下面这一行: *)
Definition manual_grade_for_mumble_grumble : option (nat×string) := None.
☐
Fixpoint repeat' X x count : list X :=
match count with
| 0 ⇒ nil X
| S count' ⇒ cons X x (repeat' X x count')
end.
确实会。我们来看看 Coq 赋予了 repeat' 什么类型:
它与 repeat 的类型完全一致。Coq 可以使用'类型推断'
基于它们的使用方式来推出 X、x 和 count 一定是什么类型。例如,
由于 X 是作为 cons 的参数使用的,因此它必定是个 Type 类型,
因为 cons 期望一个 Type 作为其第一个参数,而用 0 和 S 来匹配
count 意味着它必须是个 nat,诸如此类。
这种强大的功能意味着我们不必总是在任何地方都显式地写出类型标注,
不过显式的类型标注对于文档和完整性检查来说仍然非常有用,
因此我们仍会继续使用它。在代码中使用类型标注时,你应当把握好一个度,
太多会导致混乱并分散注意力,太少则会迫使读者为理解你的代码而在大脑中进行类型推断。
类型参数的推断
repeat' X x count : list X :=
repeat' (X : _) (x : _) (count : _) : list X :=
Fixpoint repeat'' X x count : list X :=
match count with
| 0 ⇒ nil _
| S count' ⇒ cons _ x (repeat'' _ x count')
end.
在此例中,我们写出 _ 并没有省略多少 X。然而在很多情况下,
这对减少击键次数和提高可读性还是很有效的。例如,假设我们要写下一个包含数字
1、2 和 3 的列表,此时不必写成这样:
……我们可以用洞来这样写:
隐式参数
现在我们完全不用提供类型参数了:
此外,我们还可以在定义函数时就声明隐式参数,
只需要将某个参数两边的圆括号换成花括号。例如:
Fixpoint repeat''' {X : Type} (x : X) (count : nat) : list X :=
match count with
| 0 ⇒ nil
| S count' ⇒ cons x (repeat''' x count')
end.
(注意我们现在甚至不必在 repeat''' 的递归调用中提供类型参数了,
实际上提供了反而是无效的,因为 Coq 并不想要它。)
我们会尽可能使用最后一种风格,不过还会继续在 Inductive 构造子中使用显式的
Argument 声明。原因在于如果将归纳类型的形参标为隐式的话,
不仅构造子的类型会变成隐式的,类型本身也会变成隐式的。例如,
考虑以下 list 类型的另一种定义:
由于 X 在包括 list' 本身的'整个'归纳定义中都是隐式声明的,
因此当我们讨论数值、布尔值或其它任何类型的列表时,都只能写 list',
而写不了 list' nat、list' bool 等等,这样就有点过分了。
作为本节的收尾,我们为新的多态列表重新实现几个其它的标准列表函数...
Fixpoint app {X : Type} (l1 l2 : list X)
: (list X) :=
match l1 with
| nil ⇒ l2
| cons h t ⇒ cons h (app t l2)
end.
Fixpoint rev {X:Type} (l:list X) : list X :=
match l with
| nil ⇒ nil
| cons h t ⇒ app (rev t) (cons h nil)
end.
Fixpoint length {X : Type} (l : list X) : nat :=
match l with
| nil ⇒ 0
| cons _ l' ⇒ S (length l')
end.
Example test_rev1 :
rev (cons 1 (cons 2 nil)) = (cons 2 (cons 1 nil)).
Proof. reflexivity. Qed.
Example test_rev2:
rev (cons true nil) = cons true nil.
Proof. reflexivity. Qed.
Example test_length1: length (cons 1 (cons 2 (cons 3 nil))) = 3.
Proof. reflexivity. Qed.
显式提供类型参数
(Definition 前面的 Fail 限定符可用于'任何'指令,
它的作用是确保该指令在执行时确实会失败。如果该指令失败了,Coq
就会打印出相应的错误信息,不过之后会继续处理文件中剩下的部分。)
在这里,Coq 给出了一条错误信息,因为它不知道应该为 nil 提供何种类型。
我们可以为它提供个显式的类型声明来帮助它,这样 Coq 在“应用”nil
时就有更多可用的信息了:
此外,我们还可以在函数名前加上前缀 @ 来强制将隐式参数变成显式的:
使用参数推断和隐式参数,我们可以为列表定义和前面一样的简便记法。
由于我们让构造子的的类型参数变成了隐式的,因此 Coq
就知道在我们使用该记法时自动推断它们了。
Notation "x :: y" := (cons x y)
(at level 60, right associativity).
Notation "[ ]" := nil.
Notation "[ x ; .. ; y ]" := (cons x .. (cons y []) ..).
Notation "x ++ y" := (app x y)
(at level 60, right associativity).
现在列表就能写成我们希望的方式了:
Theorem app_nil_r : ∀ (X:Type), ∀ l:list X,
l ++ [] = l.
Proof.
(* 请在此处解答 *) Admitted.
Theorem app_assoc : ∀ A (l m n:list A),
l ++ m ++ n = (l ++ m) ++ n.
Proof.
(* 请在此处解答 *) Admitted.
Lemma app_length : ∀ (X:Type) (l1 l2 : list X),
length (l1 ++ l2) = length l1 + length l2.
Proof.
(* 请在此处解答 *) Admitted.
☐
Theorem rev_app_distr: ∀ X (l1 l2 : list X),
rev (l1 ++ l2) = rev l2 ++ rev l1.
Proof.
(* 请在此处解答 *) Admitted.
Theorem rev_involutive : ∀ X : Type, ∀ l : list X,
rev (rev l) = l.
Proof.
(* 请在此处解答 *) Admitted.
☐
和列表一样,我们也可以将类型参数定义成隐式的,
并以此定义类似的具体记法:
我们也可以使用 Notation 来定义标准的'积类型(Product Types)'记法:
(标注 : type_scope 会告诉 Coq 该缩写只能在解析类型而非表达式时使用。
这避免了与乘法符号的冲突。)
一开始会很容易混淆 (x,y) 和 X×Y。不过要记住 (x,y)
是一个'值',它由两个其它的值构造得来;而 X×Y 是一个'类型',
它由两个其它的类型构造得来。如果 x 的类型为 X 而 y 的类型为 Y,
那么 (x,y) 的类型就是 X×Y。
第一元(first)和第二元(second)的射影函数(Projection Functions)
现在看起来和其它函数式编程语言中的很像了:
Definition fst {X Y : Type} (p : X × Y) : X :=
match p with
| (x, y) ⇒ x
end.
Definition snd {X Y : Type} (p : X × Y) : Y :=
match p with
| (x, y) ⇒ y
end.
以下函数接受两个列表,并将它们结合成一个序对的列表。
在其它函数式语言中,它通常被称作 zip。我们为了与 Coq 的标准库保持一致,
将它命名为 combine。
Fixpoint combine {X Y : Type} (lx : list X) (ly : list Y)
: list (X×Y) :=
match lx, ly with
| [], _ ⇒ []
| _, [] ⇒ []
| x :: tx, y :: ty ⇒ (x, y) :: (combine tx ty)
end.
练习:1 星, standard, optional (combine_checks)
请尝试在纸上回答以下问题并在 Coq 中检验你的解答:- combine 的类型是什么?(即 Check @combine 会打印出什么?)
- 以下指令会打印出什么?
Compute (combine [1;2] [false;false;true;true]).
练习:2 星, standard, recommended (split)
函数 split 是 combine 的右逆(right inverse): 它接受一个序对的列表并返回一个列表的序对。 在很多函数式语言中,它被称作 unzip。Fixpoint split {X Y : Type} (l : list (X×Y))
: (list X) × (list Y)
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Example test_split:
split [(1,false);(2,false)] = ([1;2],[false;false]).
Proof.
(* 请在此处解答 *) Admitted.
☐
多态候选
Module OptionPlayground.
Inductive option (X:Type) : Type :=
| Some (x : X)
| None.
Arguments Some {X} _.
Arguments None {X}.
End OptionPlayground.
现在我们可以重写 nth_error 函数来让它适用于任何类型的列表了。
Fixpoint nth_error {X : Type} (l : list X) (n : nat)
: option X :=
match l with
| [] ⇒ None
| a :: l' ⇒ if n =? O then Some a else nth_error l' (pred n)
end.
Example test_nth_error1 : nth_error [4;5;6;7] 0 = Some 4.
Proof. reflexivity. Qed.
Example test_nth_error2 : nth_error [[1];[2]] 1 = Some [2].
Proof. reflexivity. Qed.
Example test_nth_error3 : nth_error [true] 2 = None.
Proof. reflexivity. Qed.
再说一遍,要强制将隐式参数转为显式参数,我们可以在函数名前使用 @。
Check @hd_error : ∀ X : Type, list X → option X.
Example test_hd_error1 : hd_error [1;2] = Some 1.
(* 请在此处解答 *) Admitted.
Example test_hd_error2 : hd_error [[1];[2]] = Some [1].
(* 请在此处解答 *) Admitted.
☐
函数作为数据
这里的参数 f 本身也是个(从 X 到 X 的)函数,
doit3times 的函数体将 f 对某个值 n 应用三次。
Check @doit3times : ∀ X : Type, (X → X) → X → X.
Example test_doit3times: doit3times minustwo 9 = 3.
Proof. reflexivity. Qed.
Example test_doit3times': doit3times negb true = false.
Proof. reflexivity. Qed.
过滤器
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.
例如,如果我们将 filter 应用到谓词 evenb 和一个数值列表 l
上,那么它就会返回一个只包含 l 中偶数的列表。
Example test_filter1: filter evenb [1;2;3;4] = [2;4].
Proof. reflexivity. Qed.
Definition length_is_1 {X : Type} (l : list X) : bool :=
(length l) =? 1.
Example test_filter2:
filter length_is_1
[ [1; 2]; [3]; [4]; [5;6;7]; []; [8] ]
= [ [3]; [4]; [8] ].
Proof. reflexivity. Qed.
Definition countoddmembers' (l:list nat) : nat :=
length (filter oddb l).
Example test_countoddmembers'1: countoddmembers' [1;0;3;1;4;5] = 4.
Proof. reflexivity. Qed.
Example test_countoddmembers'2: countoddmembers' [0;2;4] = 0.
Proof. reflexivity. Qed.
Example test_countoddmembers'3: countoddmembers' nil = 0.
Proof. reflexivity. Qed.
匿名函数
表达式 (fun n ⇒ n × n) 可读作“一个给定 n 并返回 n × n 的函数。”
以下为使用匿名函数重写的 filter 示例:
Example test_filter2':
filter (fun l ⇒ (length l) =? 1)
[ [1; 2]; [3]; [4]; [5;6;7]; []; [8] ]
= [ [3]; [4]; [8] ].
Proof. reflexivity. Qed.
练习:2 星, standard (filter_even_gt7)
使用 filter(而非 Fixpoint)来编写 Coq 函数 filter_even_gt7, 它接受一个自然数列表作为输入,返回一个只包含大于 7 的偶数的列表。Definition filter_even_gt7 (l : list nat) : list nat
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Example test_filter_even_gt7_1 :
filter_even_gt7 [1;2;6;9;10;3;12;8] = [10;12;8].
(* 请在此处解答 *) Admitted.
Example test_filter_even_gt7_2 :
filter_even_gt7 [5;2;6;19;129] = [].
(* 请在此处解答 *) Admitted.
☐
练习:3 星, standard (partition)
使用 filter 编写一个 Coq 函数 partition:partition : ∀ X : Type,
(X → bool) → list X → list X × list X
Definition partition {X : Type}
(test : X → bool)
(l : list X)
: list X × list X
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Example test_partition1: partition oddb [1;2;3;4;5] = ([1;3;5], [2;4]).
(* 请在此处解答 *) Admitted.
Example test_partition2: partition (fun x ⇒ false) [5;9;0] = ([], [5;9;0]).
(* 请在此处解答 *) Admitted.
☐
Fixpoint map {X Y: Type} (f:X→Y) (l:list X) : (list Y) :=
match l with
| [] ⇒ []
| h :: t ⇒ (f h) :: (map f t)
end.
它接受一个函数 f 和一个列表 l = [n1, n2, n3, ...]
并返回列表 [f n1, f n2, f n3,...] ,其中 f 可分别应用于 l
中的每一个元素。例如:
输入列表和输出列表的元素类型不必相同,因为 map 会接受'两个'类型参数
X 和 Y,因此它可以应用到一个数值的列表和一个从数值到布尔值的函数,
并产生一个布尔值列表:
它甚至可以应用到一个数值的列表和一个从数值到布尔值列表的函数,
并产生一个布尔值的'列表的列表':
Example test_map3:
map (fun n ⇒ [evenb n;oddb n]) [2;1;2;5]
= [[true;false];[false;true];[true;false];[false;true]].
Proof. reflexivity. Qed.
Theorem map_rev : ∀ (X Y : Type) (f : X → Y) (l : list X),
map f (rev l) = rev (map f l).
Proof.
(* 请在此处解答 *) Admitted.
☐
练习:2 星, standard, recommended (flat_map)
函数 map 通过一个类型为 X → Y 的函数将 list X 映射到 list Y。 我们可以定义一个类似的函数 flat_map,它通过一个类型为 X → list Y 的函数 f 将 list X 映射到 list Y。你的定义应当可以“压扁”f 的结果,就像这样:flat_map (fun n ⇒ [n;n+1;n+2]) [1;5;10]
= [1; 2; 3; 5; 6; 7; 10; 11; 12].
Fixpoint flat_map {X Y: Type} (f: X → list Y) (l: list X)
: (list Y)
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Example test_flat_map1:
flat_map (fun n ⇒ [n;n;n]) [1;5;4]
= [1; 1; 1; 5; 5; 5; 4; 4; 4].
(* 请在此处解答 *) Admitted.
☐
Definition option_map {X Y : Type} (f : X → Y) (xo : option X)
: option Y :=
match xo with
| None ⇒ None
| Some x ⇒ Some (f x)
end.
练习:2 星, standard, optional (implicit_args)
filter 和 map 的定义和应用在很多地方使用了隐式参数。 请将隐式参数外层的花括号替换为圆括号,然后在必要的地方补充显式类型形参并用 Coq 检查你做的是否正确。(本练习并不会打分,你可以在本文件的'副本'中做它, 之后丢掉即可。) ☐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.
直观上来说,fold 操作的行为就是将给定的二元操作符 f
插入到给定列表的每一对元素之间。例如, fold plus [1;2;3;4]
直观上的意思是 1+2+3+4。为了让它更精确,我们还需要一个“起始元素”
作为 f 初始的第二个输入。因此,例如
fold plus [1;2;3;4] 0
就会产生
1 + (2 + (3 + (4 + 0))).
以下是更多例子:
fold plus [1;2;3;4] 0
1 + (2 + (3 + (4 + 0))).
Check (fold andb) : list bool → bool → bool.
Example fold_example1 :
fold mult [1;2;3;4] 1 = 24.
Proof. reflexivity. Qed.
Example fold_example2 :
fold andb [true;true;false;true] true = false.
Proof. reflexivity. Qed.
Example fold_example3 :
fold app [[1];[];[2;3];[4]] [] = [1;2;3;4].
Proof. reflexivity. Qed.
练习:1 星, advanced (fold_types_different)
我们观察到 fold 由 X 和 Y 这'两个'类型变量参数化,形参 f 则是个接受 X 和 Y 并返回 Y 的二元操作符。你能想出一种 X 和 Y 不同时的应用情景吗?(* 请在此处解答 *)
(* 请勿修改下面这一行: *)
Definition manual_grade_for_fold_types_different : option (nat×string) := None.
☐
用函数构造函数
Definition constfun {X: Type} (x: X) : nat→X :=
fun (k:nat) ⇒ x.
Definition ftrue := constfun true.
Example constfun_example1 : ftrue 0 = true.
Proof. reflexivity. Qed.
Example constfun_example2 : (constfun 5) 99 = 5.
Proof. reflexivity. Qed.
实际上,我们已经见过的多参函数也是讲函数作为数据传入的例子。
为了理解为什么,请回想 plus 的类型。
该表达式中的每个 → 实际上都是一个类型上的'二元'操作符。
该操作符是'右结合'的,因此 plus 的类型其实是 nat → (nat → nat)
的简写,即,它可以读作“plus 是一个单参数函数,它接受一个 nat
并返回另一个函数,该函数接受另一个 nat 并返回一个 nat”。
在上面的例子中,我们总是将 plus 一次同时应用到两个参数上。
不过如果我们喜欢,也可以一次只提供一个参数,这叫做'偏应用(Partial
Application)'。
Definition plus3 := plus 3.
Check plus3 : nat → nat.
Example test_plus3 : plus3 4 = 7.
Proof. reflexivity. Qed.
Example test_plus3' : doit3times plus3 0 = 9.
Proof. reflexivity. Qed.
Example test_plus3'' : doit3times (plus 3) 0 = 9.
Proof. reflexivity. Qed.
Definition fold_length {X : Type} (l : list X) : nat :=
fold (fun _ n ⇒ S n) l 0.
Example test_fold_length1 : fold_length [4;7;0] = 3.
Proof. reflexivity. Qed.
请证明 fold_length 的正确性。
(提示:知道 reflexivity 的化简力度比 simpl
更大或许会有所帮助。也就是说,你或许会遇到 simpl 无法解决但 reflexivity
可以解决的目标。)
Theorem fold_length_correct : ∀ X (l : list X),
fold_length l = length l.
Proof.
(* 请在此处解答 *) Admitted.
☐
Definition fold_map {X Y: Type} (f: X → Y) (l: list X) : list Y
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
在 Coq 中写出 fold_map_correct 来陈述 fold_map 是正确的,然后证明它。
(提示:再次提醒,reflexivity 的化简力度比 simpl 更强。)
练习:2 星, advanced (currying)
在 Coq 中,函数 f : A → B → C 的类型其实是 A → (B → C)。 也就是说,如果给 f 一个类型为 A 的值,它就会给你函数 f' : B → C。 如果再给 f' 一个类型为 B 的值,它就会返回一个类型为 C 的值。 这为我们提供了 plus3 中的那种偏应用能力。 用返回函数的函数处理参数列表的方式被称为'柯里化(Currying)', 它是为了纪念逻辑学家 Haskell Curry。
作为练习,请定义它的反函数 prod_uncurry,
然后在下面证明它们互为反函数的定理。
Definition prod_uncurry {X Y Z : Type}
(f : X → Y → Z) (p : X × Y) : Z
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
举一个柯里化用途的(平凡的)例子,我们可以用它来缩短之前看到的一个例子:
思考练习:在运行以下指令之前,你能计算出 prod_curry 和 prod_uncurry
的类型吗?
Check @prod_curry.
Check @prod_uncurry.
Theorem uncurry_curry : ∀ (X Y Z : Type)
(f : X → Y → Z)
x y,
prod_curry (prod_uncurry f) x y = f x y.
Proof.
(* 请在此处解答 *) Admitted.
Theorem curry_uncurry : ∀ (X Y Z : Type)
(f : (X × Y) → Z) (p : X × Y),
prod_uncurry (prod_curry f) p = f p.
Proof.
(* 请在此处解答 *) Admitted.
☐
练习:2 星, advanced (nth_error_informal)
回想 nth_error 函数的定义:Fixpoint nth_error {X : Type} (l : list X) (n : nat) : option X :=
match l with
| [] ⇒ None
| a :: l' ⇒ if n =? O then Some a else nth_error l' (pred n)
end.
∀ X l n, length l = n → @nth_error X l n = None
(* 请在此处解答 *)
(* 请勿修改下面这一行: *)
Definition manual_grade_for_informal_proof : option (nat×string) := None.
☐
(* 请勿修改下面这一行: *)
Definition manual_grade_for_informal_proof : option (nat×string) := None.
☐
我们来看看如何用这种记法写数。将函数迭代一次应当与将它应用一次相同。
因此:
与此类似,two 应当对其参数应用两次 f:
定义 zero 有点刁钻:我们如何“将函数应用零次”?答案很简单:
把参数原样返回就好。
更一般地说,数 n 可以写作 fun X f x ⇒ f (f
... (f x) ...),其中 f 出现了 n 次。要特别注意我们之前定义
doit3times 函数的方式就是 3 的邱奇数表示。
完成以下函数的定义。请用 reflexivity 证明来确认它们能够通过对应的单元测试。
自然数的后继:给定一个邱奇数 n,它的后继 succ n 是一个把它的参数比 n
还多迭代一次的函数。
练习:1 星, advanced (church_succ)
Definition succ (n : cnat) : cnat
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Example succ_1 : succ zero = one.
Proof. (* 请在此处解答 *) Admitted.
Example succ_2 : succ one = two.
Proof. (* 请在此处解答 *) Admitted.
Example succ_3 : succ two = three.
Proof. (* 请在此处解答 *) Admitted.
☐
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Example succ_1 : succ zero = one.
Proof. (* 请在此处解答 *) Admitted.
Example succ_2 : succ one = two.
Proof. (* 请在此处解答 *) Admitted.
Example succ_3 : succ two = three.
Proof. (* 请在此处解答 *) Admitted.
☐
Definition plus (n m : cnat) : cnat
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Example plus_1 : plus zero one = one.
Proof. (* 请在此处解答 *) Admitted.
Example plus_2 : plus two three = plus three two.
Proof. (* 请在此处解答 *) Admitted.
Example plus_3 :
plus (plus two two) three = plus one (plus three three).
Proof. (* 请在此处解答 *) Admitted.
☐
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Example plus_1 : plus zero one = one.
Proof. (* 请在此处解答 *) Admitted.
Example plus_2 : plus two three = plus three two.
Proof. (* 请在此处解答 *) Admitted.
Example plus_3 :
plus (plus two two) three = plus one (plus three three).
Proof. (* 请在此处解答 *) Admitted.
☐
Definition mult (n m : cnat) : cnat
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Example mult_1 : mult one one = one.
Proof. (* 请在此处解答 *) Admitted.
Example mult_2 : mult zero (plus three three) = zero.
Proof. (* 请在此处解答 *) Admitted.
Example mult_3 : mult two three = plus three three.
Proof. (* 请在此处解答 *) Admitted.
☐
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Example mult_1 : mult one one = one.
Proof. (* 请在此处解答 *) Admitted.
Example mult_2 : mult zero (plus three three) = zero.
Proof. (* 请在此处解答 *) Admitted.
Example mult_3 : mult two three = plus three three.
Proof. (* 请在此处解答 *) Admitted.
☐
练习:2 星, advanced (church_exp)
Definition exp (n m : cnat) : cnat
(* 将本行替换成 ":= _你的_定义_ ." *). Admitted.
Example exp_1 : exp two two = plus two two.
Proof. (* 请在此处解答 *) Admitted.
Example exp_2 : exp three zero = one.
Proof. (* 请在此处解答 *) Admitted.
Example exp_3 : exp three two = plus (mult two (mult two two)) one.
Proof. (* 请在此处解答 *) Admitted.
☐