我正在浏览软件基础这本书来学习 Coq ,我被卡在Numbers上。在此类型定义中
Inductive nat : Type :=
| O : nat
| S : nat -> nat.
我们在
的定义中使用它时,O
如何成为0
Definition pred (n : nat) : nat :=
match n with
| O => O
| S n' => n'
end.
我只知道O
表示自然数,而S
表示自然数并返回另一个自然数。我不知道的是,在nat
定义中使用我们定义的pred
数据类型O
如何代表0
? S n'
模式匹配如何给我们n
的前身。
答案 0 :(得分:2)
nat
代表unary numeral system的自然数字。 nat
类型的居民(值)为O
,S O
,S (S O)
,S (S (S O))
,...,S (S (S (S ... O)...)
。
我们将符号O
解释为自然数零。 S
表示单个" tick"在一元表示中,即我们将S
解释为构造函数(它与面向对象编程无关),它采用自然数并产生下一个自然数。
pred
实际上并不是一个表现良好的功能。
首先,当我们谈论自然数时,没有前导零。但是Coq中的所有函数都必须返回一个值,所以如果我们要保持pred
的类型(nat -> nat
),我们必须对pred O
做一些事情。只需返回O
并将其完成即可。这为我们提供了pattern-mathing表达式的第一个分支(O => O
)。
接下来,当我们在代表1的数字上调用pred
时,我们会返回什么?让我们回想一下,我们在Coq中用S O
写了1。这很容易 - 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'
带到{{1 }}。我们如何检查我们在这里没有犯错?如果我们将S (S O)
的定义替换为n'
,我们应该取回原S n'
:n
= S n'
= S (S (S O))
。
现在,我们只返回n
:
n'
正如预期的那样,我们得到了2个结果!
0,S (S O)
和0
之间存在区别。第一个零(0)是自然数零的元级数字(它是我们如何在我们的元语言中指定零,例如在这种情况下为英语)。 O
是0
的表示法,换句话说O
是0
的句法糖。正如Coq参考手册所说(§1.2.4):
数字在微积分中没有明确的语义。它们只是通过符号机制绑定到对象的符号(详见第12章)。最初,数字与Peano对自然数的表示有关(见3.1.3)。
很容易说明:
O
你可以重载数字,这是一个整数的例子:
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 *)
Upshot:相同的符号可以绑定到不同的术语。 Coq定义了一些默认符号,例如像数字这样无处不在的东西。
如果您想定义自己的类型来表示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 *)
和my_nat
可能有不同名称的自然数字{O
)(例如S
和{{1} }}),你必须编写一个插件来将Coq数字映射到stop
的条款上(参见here)。