皮亚诺数
nat
通过一元数字系统表示自然数。该nat
类型的居民(值)是O
, S O
, S (S O)
, S (S (S O))
, ..., S (S (S (S ... O)...)
。
我们将符号解释O
为自然数零。并S
以一元表示形式表示单个“滴答”,即我们将其解释S
为构造函数(它与面向对象编程无关),它接受一个自然数并产生下一个自然数。
前置函数
pred
在某种意义上实际上并不是一个非常乖巧的函数。
首先,当我们谈论自然数时,没有零的前身。但是 Coq 中的所有函数都必须返回一个值,所以如果我们想保留pred
(即nat -> nat
)的类型,我们必须对pred O
. 回来O
并结束它感觉很自然。这给了我们模式数学表达式 ( O => O
) 的第一个分支。
pred
接下来,当我们调用代表 1 的数字时,我们返回了什么?让我们回想一下,我们在 Coq 中将 1 写为S O
。这很容易——pred (S O)
应该返回O
。现在让我们尝试 2:pred (S (S O))
应该返回S O
. 你看到这里的模式了吗?S
如果我们在一个' 前面有一堆' O
,我们就去掉一个S
——就这么简单。模式匹配表达式的第二个分支 ( S n' => n'
) 正是这样做的:它采用一个(非零)形式的数字S n'
并将其转换为n'
(当然不改变原始形式)。
让我举个例子。让我们一步一步计算数字3的前身:
pred (S (S (S O)))
展开 的定义,pred
代替S (S (S O))
:n
match S (S (S O)) with
| O => O
| S n' => n'
end
S (S (S O))
有形式S n'
(以 开头S
),所以我们采用第二个分支,绑定 n'
到S (S O)
. 我们如何检查我们没有在这里犯错?如果我们替换n'
into的定义,S n'
我们应该得到原来的n
: S n'
= S (S (S O))
= n
。
现在,我们只需返回n'
:
S (S O)
正如预期的那样,我们得到了 2 作为结果!
符号
0
0、和之间有区别O
。第一个零 (0) 是自然数零的元级数字(这是我们在元语言中指定零的方式,例如本例中的英语)。0
是 的符号O
,换句话说,0
是 的语法糖O
。正如 Coq 参考手册所说(§1.2.4):
数词在微积分中没有明确的语义。它们只是可以通过符号机制绑定到对象的符号(详见第 12 章)。最初,数字被绑定到 Peano 对自然数的表示(见 3.1.3)。
很容易说明:
Check 0. (* 0 : nat *)
Check 5. (* 5 : nat *)
Unset Printing Notations. (* Print terms as is, no syntactic sugar *)
Check 0. (* O : nat *)
Check 5. (* S (S (S (S (S O)))) : nat *)
您可以重载数字,这是一个整数示例:
Set Printing Notations.
Require Import Coq.ZArith.ZArith.
Open Scope Z.
Check 0. (* 0 : Z *)
Check 5. (* 5 : Z *)
Unset Printing Notations.
Check 0. (* Z0 : Z *)
Check 5. (* Zpos (xI (xO xH)) : Z *)
结果:相同的符号可以绑定到不同的术语。Coq 定义了一些默认符号,例如数字等无处不在的事物。
如果你想定义你自己的类型来表示自然数 ( my_nat
) 可能与O
和S
(like stop
and tick
) 的名称不同,你必须编写一个插件来将 Coq 数字映射到my_nat
(see here ) 的术语上。