[英]How can a coq Set or Type be a proposition
I'm reading a tutorial on Coq. 我正在阅读关于Coq的教程。 It constructs a
bool
type as follows: 它构造一个
bool
类型如下:
Coq < Inductive bool : Set := true | false.
bool is defined
bool_rect is defined
bool_ind is defined
bool_rec is defined
Then it shows what each of these things are using "Check". 然后它显示了这些东西正在使用“检查”。
Coq < Check bool_ind.
bool_ind
: forall P : bool -> Prop, P true -> P false -> forall b : bool, P b
Coq < Check bool_rec.
bool_rec
: forall P : bool -> Set, P true -> P false -> forall b : bool, P b
Coq < Check bool_rect.
bool_rect
: forall P : bool -> Type, P true -> P false -> forall b : bool, P b
I understand bool_ind
. 我理解
bool_ind
。 It says that if something holds for true
and it holds for false
, then it holds for all b
in bool
(because those are the only two). 它说,如果某些东西保持为
true
并且它保持为false
,那么它适用于bool
所有b
(因为那些是唯一的两个)。
But I don't understand what the expressions for bool_rec
or bool_rect
mean. 但我不明白
bool_rec
或bool_rect
的表达是什么意思。 It seems as if P true
(which is a Set
for bool_rec
and a Type
for bool_rect
) is being treated as a propositional value. 看来,如果
P true
(这是一Set
用于bool_rec
和Type
的bool_rect
)被视为一个命题值。 What am I missing here? 我在这里错过了什么?
Your intuition for bool_ind
is spot on, but thinking about why bool_ind
means what you said might help clarify the other two. 你对
bool_ind
直觉bool_ind
明显,但是考虑为什么bool_ind
意味着你所说的可能有助于澄清其他两个。 We know that 我们知道
bool_ind : forall P : bool -> Prop,
P true ->
P false ->
forall b : bool,
P b
If we read this as a logical formula, we get the same reading you did: 如果我们将其视为逻辑公式,我们会得到您所做的相同读数:
P
on bool
eans, bool
eans中的每个谓词P
,
P true
holds, and P true
,则 P false
holds, then P false
成立,那么 b
, b
,
P b
holds. P b
成立。 But this isn't just a logical formula, it's a type. 但这不仅仅是一个逻辑公式,而是一种类型。 Specifically, it's a (dependent) function type.
具体来说,它是(依赖)函数类型。 And as a function type, it says (if you'll allow me the liberty of inventing names for the unnamed arguments and the result):
作为一个函数类型,它说(如果你允许我为未命名的参数和结果发明名称的自由):
P : bool -> Prop
, P : bool -> Prop
,
Pt : P true
, Pt : P true
, Pf : P false
, and Pf : P false
,和 b : bool
, b : bool
,
Pb : P b
. Pb : P b
。 (Of course, this is a curried function, so there are other ways to break down the type into prose, but this is clearest for our purposes.) (当然,这是一个curry函数,所以还有其他方法可以将类型分解为散文,但这对我们的目的来说是最清楚的。)
The big important thing here, the thing that makes Coq work as a theorem prover while being a programming language (or vice versa) is the Curry-Howard correspondence : types are propositions, and values are proofs of those propositions. 这里最重要的是,使Coq作为一个定理证明者而作为一种编程语言(反之亦然)的事情是Curry-Howard的对应关系 :类型是命题,值是这些命题的证明。 For instance, the simple function type
->
corresponds to implication, and the dependent function type forall
corresponds to universal quantification. 例如,简单函数类型
->
对应于蕴涵,依赖函数类型forall
对应于通用量化。 (The notation is pretty suggestive :-)) So in Coq, to prove that φ → ψ, we must construct a value of type φ -> ψ
: a function that takes a value of type φ
(or in other words, a proof of the proposition φ) and uses it to construct a value of type ψ
(a proof of the proposition ψ). (符号很有启发性:-))所以在Coq中,为了证明φ→ψ,我们必须构造一个
φ -> ψ
类型的值:一个取φ
型值的函数(换句话说,一个证明命题φ)并用它来构造ψ
类型的值(命题ψ的证明)。
In Coq, we can think about all types in this way, whether those types live in Set
, Type
, or Prop
. 在Coq中,我们可以通过这种方式考虑所有类型,无论这些类型是否存在于
Set
, Type
或Prop
。 (So when you say "It seems as if P true (which is a Set for bool rec and a Type for bool_rect) is being treated as a propositional value," you're right!) For instance, let's consider how we'd implement bool_ind
ourselves. (所以当你说“似乎P true(这是bool rec的设置和bool_rect的类型)被视为命题值时,”你是对的!)例如,让我们考虑一下我们是怎么做的我们自己实现
bool_ind
。 We'll start by listing all the parameters to the function, along with its return type: 我们首先列出函数的所有参数及其返回类型:
Definition bool_ind' (P : bool -> Prop)
(Pt : P true)
(Pf : P false)
(b : bool)
: P b :=
So far, so good. 到现在为止还挺好。 At this point, we'd like to return something of type
P b
, but we don't know what b
is. 在这一点上,我们想要返回
P b
类型的东西,但我们不知道b
是什么。 So, as always in these situations, we pattern match: 所以,在这些情况下,我们总是模式匹配:
match b with
There are now two cases. 现在有两个案例。 First,
b
could be true
. 首先,
b
可能是true
。 In this case, we must want to return something of type P true
, and luckily we have such a value: Pt
. 在这种情况下,我们必须要返回
P true
类型为P true
东西,幸运的是我们有这样的值: Pt
。
| true => Pt
The false
case is similar: false
案件类似:
| false => Pf
end.
Note that when we implement bool_ind'
, it doesn't look very "proofy," but rather very "programmy". 请注意,当我们实现
bool_ind'
,它看起来并不“非常有用”,而是非常“程序化”。 Of course, thanks to the Curry-Howard correspondence, these are the same. 当然,由于Curry-Howard的通信,这些都是一样的。 But note that the very same implementation will suffice for the other two functions:
但请注意,完全相同的实现将足以满足其他两个功能:
Definition bool_rec' (P : bool -> Set)
(Pt : P true)
(Pf : P false)
(b : bool)
: P b :=
match b with
| true => Pt
| false => Pf
end.
Definition bool_rect' (P : bool -> Type)
(Pt : P true)
(Pf : P false)
(b : bool)
: P b :=
match b with
| true => Pt
| false => Pf
end.
Looking at this computational definition exposes another way to thing about bool_ind
, bool_rec
, and bool_rect
: they encapsulate what you need to know to talk about every value of bool
. 看看这个计算定义揭示了另一种关于
bool_ind
, bool_rec
和bool_rect
:它们封装了你需要知道的东西来讨论bool
每个值 。 But either way, we're packaging up that information: if I know something for true
, and something for false
, then I know it for all the bool
s. 但不管怎样,我们正在打包那些信息:如果我知道某些事情是
true
,而某些事情是false
,那么我知道所有的bool
。
The definition of the bool_{ind,rec,rect}
functions abstracts over the usual way we write functions on booleans: there's one argument corresponding to the true branch, and one to the false branch. bool_{ind,rec,rect}
函数的定义通过我们在布尔值上编写函数的常规方式进行抽象:有一个参数对应于true分支,一个参数对应于false分支。 Or, in other words: these functions are just if
statements. 或者,换句话说:这些函数只是
if
语句。 In a non–dependently-typed language, they could have the simpler type forall S : Set, S -> S -> bool -> S
: 在非依赖类型的语言中,它们可以具有更简单的类型
forall S : Set, S -> S -> bool -> S
:
Definition bool_simple_rec (S : Set) (St : P) (Sf : P) (b : bool) : S :=
match b with
| true => St
| false => Sf
end.
However, because types can depend on values, we must thread the b
through the types everywhere. 但是,因为类型可以依赖于值,所以我们必须通过各种类型来处理
b
。 If it turns out we don't want that, though, we can use our more general function and tell : 如果事实证明我们不希望这样,我们可以使用我们更通用的功能并告诉:
Definition bool_simple_rec' (S : Set) : S -> S -> bool -> S :=
bool_rec (fun _ => S).
Nobody ever said our P : bool -> Set
had to use the bool
! 没有人说我们的
P : bool -> Set
必须使用 bool
!
All of these functions are a lot more interesting for recursive types. 对于递归类型,所有这些函数都更有趣。 For instance, Coq has the following type of natural numbers:
例如,Coq具有以下类型的自然数:
Inductive nat : Set := O : nat | S : nat -> nat.
And we have 我们有
nat_ind : forall P : nat -> Prop,
P O ->
(forall n' : nat, P n' -> P (S n')) ->
forall n : nat,
P n
Along with the corresponding nat_rec
and nat_rect
. 连同相应的
nat_rec
和nat_rect
。 (Exercise for the reader: implement these functions directly.) (为读者练习:直接实现这些功能。)
At first glance, this is just the principle of mathematical induction. 乍一看,这只是数学归纳的原理。 However, it's also how we write recursive functions on
nat
s; 但是,它也是我们在
nat
上编写递归函数的方式; they're the same thing. 他们是一回事。 In general, recursive functions over
nat
look like the following: 通常,
nat
递归函数如下所示:
fix f n => match n with
| O => ...
| S n' => ... f n' ...
end
The arm of the match following O
(the base case) is just the value of type PO
. O
(基本情况)之后的匹配臂只是PO
类型的值。 The arm of the match following S n'
(the recursive case) is what's passed into the function of type forall n' : nat, P n' -> P (S n')
: the n'
s are the same, and the value of P n'
is the result of the recursive call f n'
. S n'
之后的匹配臂(递归情况)是传递给类型forall n' : nat, P n' -> P (S n')
的函数forall n' : nat, P n' -> P (S n')
: n'
是相同的,并且P n'
值是递归调用f n'
。
Another way to think about the equivalence between the _rec
and _ind
functions, then—and one which I think is clearer on infinite types than on bool
—is that it's the same as the equivalence between mathematical ind
uction (which happens in Prop
) and (structural) rec
ursion (which happens in Set
and Type
). 考虑
_rec
和_ind
函数之间的等价性的另一种方法,然后 - 我认为在无限类型上比在bool
上更清楚 - 它与数学ind
(在Prop
中发生)和(和)之间的等价性相同结构) rec
ursion(发生在Set
和Type
)。
Let's get praxic and use these functions. 让我们充满热情并使用这些功能。 We'll define a simple function that converts booleans to natural numbers, and we'll do it both directly and with
bool_rec
. 我们将定义一个简单的函数,将布尔值转换为自然数,我们将直接和
bool_rec
一起bool_rec
。 The simplest way to write this function is with a pattern match: 编写此函数的最简单方法是使用模式匹配:
Definition bool_to_nat_match (b : bool) : nat :=
match b with
| true => 1
| false => 0
end.
The alternative definition is 另一种定义是
Definition bool_to_nat_rec : bool -> nat :=
bool_rec (fun _ => nat) 1 0.
And these two functions are the same: 这两个功能是一样的:
Goal bool_to_nat_match = bool_to_nat_rec.
Proof. reflexivity. Qed.
(Note: these functions are syntactically equal . This is a stronger condition than simply doing the same thing.) (注意:这些函数在语法上是相同的 。这比简单地做同样的事情更强大。)
Here, the P : bool -> Set
is fun _ => nat
; 在这里,
P : bool -> Set
很fun _ => nat
; it gives us the return type, which isn't dependent on the argument. 它给我们返回类型,它不依赖于参数。 Our
Pt : P true
is 1
, the thing to compute when we're given true
; 我们的
Pt : P true
是1
,当我们给出true
时计算的东西; similarly, our Pf : P false
is 0
. 同样,我们的
Pf : P false
为0
。
If we want to use the dependency, we have to cook up a useful data type. 如果我们想要使用依赖项,我们必须编写一个有用的数据类型。 How about
怎么样
Inductive has_if (A : Type) : bool -> Type :=
| has : A -> has_if A true
| lacks : has_if A false.
With this definition, has_if A true
is isomorphic to A
, and has_if A false
is isomorphic to unit
. 根据这个定义,
has_if A true
与A
同构,而has_if A false
是与unit
同构的。 We could then have a function which retains its first argument if and only if it's passed true
. 然后我们可以有一个函数,当且仅当它被传递为
true
时才保留它的第一个参数。
Definition keep_if_match' (A : Type) (a : A) (b : bool) : has_if A b :=
match b with
| true => has A a
| false => lacks A
end.
The alternative definition is 另一种定义是
Definition keep_if_rect (A : Type) (a : A) : forall b : bool, has_if A b :=
bool_rect (has_if A) (has A a) (lacks A).
And they're again the same: 他们又是一样的:
Goal keep_if_match = keep_if_rect.
Proof. reflexivity. Qed.
Here, the return type of the function is dependent upon the argument b
, so our P : bool -> Type
actually does something. 在这里,函数的返回类型取决于参数
b
,所以我们的P : bool -> Type
实际上做了什么。
Here's a more interesting example, using natural numbers and length-indexed lists. 这是一个更有趣的例子,使用自然数和长度索引列表。 If you haven't seen length-indexed lists, also called vectors, they're exactly what they say on the tin;
如果你还没有看到长度索引列表,也称为矢量,它们就像他们在锡上所说的那样;
vec A n
is a list of n
A
s. vec A n
是n
A
的列表。
Inductive vec (A : Type) : nat -> Type :=
| vnil : vec A O
| vcons : forall n, A -> vec A n -> vec A (S n).
Arguments vnil {A}.
Arguments vcons {A n} _ _.
(The Arguments
machinery handles implicit arguments.) Now, we want to produce a list of n
copies of some particular element, so we can write that with a fixpoint: (
Arguments
机制处理隐式参数。)现在,我们想要生成一个特定元素的n
副本的列表,因此我们可以使用fixpoint编写它:
Fixpoint vreplicate_fix {A : Type} (n : nat) (a : A) : vec A n :=
match n with
| O => vnil
| S n' => vcons a (vreplicate_fix n' a)
end.
Alternatively, we can use nat_rect
: 或者,我们可以使用
nat_rect
:
Definition vreplicate_rect {A : Type} (n : nat) (a : A) : vec A n :=
nat_rect (vec A) vnil (fun n' v => vcons a v) n.
Note that since nat_rect
captures the recursion pattern, vreplicate_rect
is not a fixpoint itself. 请注意,由于
nat_rect
捕获递归模式,因此vreplicate_rect
本身不是固定点。 One thing to note is the third argument to nat_rect
: 需要注意的一点是
nat_rect
的第三个参数:
fun n' v => vcons a v
The v
there is conceptually the result of the recursive call to vreplicate_rect n' a
; v
在概念上是对vreplicate_rect n' a
的递归调用的结果; nat_rect
abstracts out that recursion pattern, so we don't need to call it directly. nat_rect
抽象出递归模式,因此我们不需要直接调用它。 The n'
is indeed the same n'
as in vreplicate_fix
, but now it seems like we don't need to mention it explicitly. n'
与vreplicate_fix
的n'
确实相同,但现在看来我们不需要明确提及它。 Why is it passed in? 它为什么传入? That becomes clear if we write out our types:
如果我们写出我们的类型,那就很明显了:
fun (n' : nat) (v : vec A n') => vcons a v : vec A (S n')
We need n'
so we know what type v
has, and consequently what type the result has. 我们需要
n'
所以我们知道v
有什么类型,结果是什么类型的结果。
Let's see these functions in action: 让我们看看这些功能在起作用:
Eval simpl in vreplicate_fix 0 tt.
Eval simpl in vreplicate_rect 0 tt.
(* both => = vnil : vec unit 0 *)
Eval simpl in vreplicate_fix 3 true.
Eval simpl in vreplicate_rect 3 true.
(* both => = vcons true (vcons true (vcons true vnil)) : vec bool 3 *)
And indeed, they're the same: 事实上,他们是一样的:
(* Note: these two functions do the same thing, but are not syntactically
equal; the former is a fixpoint, the latter is a function which returns a
fixpoint. This sort of equality is all you generally need in practice. *)
Goal forall (A : Type) (a : A) (n : nat),
vreplicate_fix n a = vreplicate_rect n a.
Proof. induction n; [|simpl; rewrite IHn]; reflexivity. Qed.
Above, I posed the exercise of reimplementing nat_rect
and friends. 上面,我提出重新实现
nat_rect
和朋友的练习。 Here's the answer: 这是答案:
Fixpoint nat_rect' (P : nat -> Type)
(base_case : P 0)
(recurse : forall n', P n' -> P (S n'))
(n : nat)
: P n :=
match n with
| O => base_case
| S n' => recurse n' (nat_rect' P base_case recurse n')
end.
This hopefully makes it clear just how nat_rect
abstracts the recursion pattern, and why it's sufficiently general. 这有希望清楚地表明
nat_rect
如何抽象递归模式,以及为什么它足够通用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.