我正在阅读CPDT并且有一个我不理解的例子:
Definition plus_rec : nat -> nat -> nat :=
nat_rec (fun _ : nat => nat -> nat) (fun m => m) (fun _ r m => S (r m)).
除了fun _ : nat => nat -> nat
之外,大部分内容都有意义。我不明白这有什么意义,这个函数似乎返回一个没有值的类型。
我是否误解了Coq的基本内容?那个lambda有什么意义?
答案 0 :(得分:4)
我们来看看nat_rec
:
nat_rec :
forall P : nat -> Set,
P O ->
(forall n : nat, P n -> P (S n)) ->
forall n : nat, P n
它的第一个参数是P
,给定nat
,返回Set
。然后你将给出集合P O
的元素,以及集合P (S n)
的元素,假设你有P n
的元素,最后有了所有这些,你会得到回报forall n, P n
类型的东西,这意味着它可以为你传入的每个P n
构造一个n
类型的元素作为输入(如果你传递O,它将会返回{{1}你提供了,否则它将通过重复应用第三个参数来构建你的结果。)
现在,令你困惑的那个lambda是{I}我一直在谈论的P O
。给它一个P
并从中构建“返回类型”。我们特别尝试将返回类型nat
,即nat_rec
作为forall n : nat, P n
的类型,即plus_rec
。这可以通过实例化nat -> nat -> nat
来完成,以便对所有n P
进行实例化。这是我们使用lambda-term P n = nat -> nat
获得的。
此术语忽略了输入fun _ : nat => nat -> nat
(因为nat
通常足以构建类型取决于它们收到的第一个nat_rec
,但部分nat
类型的内容应用于任何第一个参数实际上并不取决于该参数的值)。然后它只返回plus_rec
(这是添加到一个操作数的部分应用的类型:在我们返回总和nat -> nat
之前,我们仍然期望nat
。
现在,如果你看看nat
部分应用于那个有趣的lambda术语,那么类型是:
nat_rec
(我刚刚用nat_rec (fun _ : nat => nat -> nat) :
(fun _ : nat => nat -> nat) O ->
(forall n : nat, (fun _ : nat => nat -> nat) n -> (fun _ : nat => nat -> nat) (S n)) ->
forall n : nat, (fun _ : nat => nat -> nat) n
回复P
。现在,这简化为:
(fun _ : nat => nat -> nat)
现在这肯定会令人困惑。
您应该考虑作为nat_rec (fun _ : nat => nat -> nat) :
(nat -> nat) ->
(forall n : nat, (nat -> nat) -> (nat -> nat)) ->
forall n : nat, (nat -> nat)
到plus
的部分应用类型的第一个参数。它接收第二个操作数,并返回两者的总和:
O
第二个参数是(fun m => m) (* morally, O + m *)
,函数(n : nat)
,另一个r : nat -> nat
,需要返回最终m : nat
。这是一堆中最令人困惑的。在道德上,nat
是一个r
,也就是我们的目标,“一个知道如何在其输入中添加n的函数”。在道德上,最后P n
应该是(nat -> nat)
,即“一个知道如何向其输入添加(S n)的函数”。特别是,我们将此输入命名为P (S n)
。你应该能够说服自己,结果应该是:
m
通过这一切,我们设法构建了函数(fun _ r m => S (r m))
(* ignore n, use r (which is the "+ n" function) on m, to get (n + m), and add 1 to get (n + 1) + m *)
。
请注意,这是一种非常容易出错的定义plus
的方法,特别是因为你必须非常难以记住什么是或者你不会定义你想要的功能,因为输入真的很松散(看看我们大多数时间忽略plus_rec
)。实际上,同一函数的早期草案中存在一个错误,它进行了类型检查。
我相信这是为了展示一切如何在地毯下,但不要认为这是你应该定义plus_rec的方式。
最后,这很难解释,我很确定我的答案不会很清楚。随意删除评论,我会编辑答案,使事情变得更加干净。