coq Set或Type如何成为命题

dsp*_*pyz 12 types set boolean-expression coq first-order-logic

我正在阅读关于Coq的教程.它构造一个bool类型如下:

Coq < Inductive bool :  Set := true | false.
bool is defined
bool_rect is defined
bool_ind is defined
bool_rec is defined
Run Code Online (Sandbox Code Playgroud)

然后它显示了这些东西正在使用"检查".

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
Run Code Online (Sandbox Code Playgroud)

我明白了bool_ind.它说,如果事情适用于true它适用于false,那么它适用于所有bbool(因为这些是仅有的两个).

但我不明白表达的含义bool_recbool_rect意思.似乎P true(这是一个Setfor bool_recTypefor bool_rect)被视为一个命题值.我在这里错过了什么?

Ant*_*sky 17

你的直觉bool_indbool_ind现实,但思考为什么意味着你所说的可能有助于澄清其他两个.我们知道

bool_ind : forall P : bool -> Prop,
             P true ->
             P false ->
             forall b : bool,
               P b
Run Code Online (Sandbox Code Playgroud)

如果我们将其视为逻辑公式,我们会得到您所做的相同读数:

  • 对于每个P关于booleans的谓词,
    • 如果P true持有,和
    • 如果P false成立,那么
    • 对于每个布尔值b,
      • P b 成立.

但这不仅仅是一个逻辑公式,而是一种类型.具体来说,它是(依赖)函数类型.作为一个函数类型,它说(如果你允许我为未命名的参数和结果发明名称的自由):

  • 鉴于价值P : bool -> Prop,
    • 一个价值Pt : P true,
    • 一个值Pf : P false,和
    • 一个价值b : bool,
      • 我们可以构建一个值Pb : P b.

(当然,这是一个curry函数,所以还有其他方法可以将类型分解为散文,但这对我们的目的来说是最清楚的.)

这里最重要的是,使Coq作为一个定理证明者而作为一种编程语言(反之亦然)的事情是Curry-Howard的对应关系:类型是命题,值是这些命题的证明.例如,简单函数类型->对应于暗示,并且从属函数类型forall对应于通用量化.(符号很有启发性:-))所以在Coq中,为了证明φ→ψ,我们必须构造一个类型的值? -> ?:一个取值类型的函数?(换句话说,就是命题φ的证明)并用它来构造一个类型的值?(命题ψ的证明).

在勒柯克,我们可以想想这样的所有类型,这些类型是否住Set,TypeProp.(所以当你说"似乎P true(这是bool rec的设置和bool_rect的类型)被视为命题值时,"你是对的!)例如,让我们考虑一下我们是怎么做的实施bool_ind自己.我们首先列出函数的所有参数及其返回类型:

Definition bool_ind' (P  : bool -> Prop)
                     (Pt : P true)
                     (Pf : P false)
                     (b  : bool)
                     : P b :=
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.在这一点上,我们想要返回一些类型P b,但我们不知道是什么b.所以,在这些情况下,我们总是模式匹配:

  match b with
Run Code Online (Sandbox Code Playgroud)

现在有两个案例.首先,b可能是true.在这种情况下,我们必须要返回类型的东西P true,幸运的是我们有这样的值:Pt.

    | true  => Pt
Run Code Online (Sandbox Code Playgroud)

false情况是类似的:

    | false => Pf
  end.
Run Code Online (Sandbox Code Playgroud)

请注意,当我们实现时bool_ind',它看起来不是"证明",而是非常"程序化".当然,由于Curry-Howard的通信,这些都是一样的.但请注意,完全相同的实现将足以满足其他两个功能:

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.
Run Code Online (Sandbox Code Playgroud)

看着这个计算定义暴露出另一种方式件事bool_ind,bool_recbool_rect:它们封装你需要知道谈论什么的每一个值bool.但无论哪种方式,我们都在打包这些信息:如果我知道某些事情true,以及某些事情false,那么我知道所有bool的信息.

bool_{ind,rec,rect}函数的定义通过我们在布尔值上编写函数的通常方式进行抽象:有一个参数对应于真正的分支,一个参数对应于假分支.换句话说:这些函数只是if语句.在非依赖类型的语言中,它们可以具有更简单的类型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.
Run Code Online (Sandbox Code Playgroud)

但是,因为类型可以依赖于值,所以我们必须b遍历各地的类型.如果事实证明我们不希望这样,我们可以使用我们更通用的功能并告诉:

Definition bool_simple_rec' (S : Set) : S -> S -> bool -> S :=
  bool_rec (fun _ => S).
Run Code Online (Sandbox Code Playgroud)

从来没有人说,我们P : bool -> Set不得不使用bool!

对于递归类型,所有这些函数都更有趣.例如,Coq具有以下类型的自然数:

Inductive nat : Set :=  O : nat | S : nat -> nat.
Run Code Online (Sandbox Code Playgroud)

我们有

nat_ind : forall P : nat -> Prop,
            P O ->
            (forall n' : nat, P n' -> P (S n')) ->
            forall n : nat,
              P n
Run Code Online (Sandbox Code Playgroud)

随着相应的nat_recnat_rect.(为读者练习:直接实现这些功能.)

乍一看,这只是数学归纳的原理.但是,它也是我们在nats 上编写递归函数的方式; 他们是一回事.通常,递归函数nat如下所示:

fix f n => match n with
             | O    => ...
             | S n' => ... f n' ...
           end
Run Code Online (Sandbox Code Playgroud)

以下匹配的臂O(基本情况)只是类型的值P O.接下来的匹配S n'(递归情况)是传递给类型函数的东西forall n' : nat, P n' -> P (S n'):n's是相同的,值P n'是递归调用的结果f n'.

考虑_rec_ind函数之间的等价性的另一种方式,然后 - 我认为在无限类型上比在bool-n 上更清楚- 它与数学函数(ind发生在其中Prop)和(结构)rec函数之间的等价性相同(其中发生在SetType).


让我们充满热情并使用这些功能.我们将定义一个简单的函数,将布尔值转换为自然数,我们将直接和使用bool_rec.编写此函数的最简单方法是使用模式匹配:

Definition bool_to_nat_match (b : bool) : nat :=
  match b with
    | true  => 1
    | false => 0
  end.
Run Code Online (Sandbox Code Playgroud)

另一种定义是

Definition bool_to_nat_rec : bool -> nat :=
  bool_rec (fun _ => nat) 1 0.
Run Code Online (Sandbox Code Playgroud)

这两个功能是一样的:

Goal bool_to_nat_match = bool_to_nat_rec.
Proof. reflexivity. Qed.
Run Code Online (Sandbox Code Playgroud)

(注意:这些函数在语法上是相同的.这比简单地做同样的事情更强大.)

在这里,P : bool -> Setfun _ => nat; 它给我们返回类型,它不依赖于参数.我们Pt : P true1,当我们被给予时计算的东西true; 同样,我们Pf : P false0.

如果我们想要使用依赖项,我们必须编写一个有用的数据类型.怎么样

Inductive has_if (A : Type) : bool -> Type :=
  | has   : A -> has_if A true
  | lacks : has_if A false.
Run Code Online (Sandbox Code Playgroud)

有了这个定义,has_if A true就是同构的A,并且has_if A false是同构的unit.然后我们可以有一个函数,当且仅当它被传递时才保留它的第一个参数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.
Run Code Online (Sandbox Code Playgroud)

另一种定义是

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).
Run Code Online (Sandbox Code Playgroud)

他们又是一样的:

Goal keep_if_match = keep_if_rect.
Proof. reflexivity. Qed.
Run Code Online (Sandbox Code Playgroud)

这里,函数的返回值类型取决于参数b,所以我们P : bool -> Type实际上做的东西.

这是一个更有趣的例子,使用自然数和长度索引列表.如果你还没有看到长度索引列表,也称为矢量,它们就像他们在锡上所说的那样; vec A n是一个n As 的列表.

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} _ _.
Run Code Online (Sandbox Code Playgroud)

(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.
Run Code Online (Sandbox Code Playgroud)

或者,我们可以使用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.
Run Code Online (Sandbox Code Playgroud)

请注意,由于nat_rect捕获递归模式,vreplicate_rect因此本身不是固定点.需要注意的一点是第三个参数nat_rect:

fun n' v => vcons a v
Run Code Online (Sandbox Code Playgroud)

v概念上存在递归调用的结果vreplicate_rect n' a; nat_rect抽象出递归模式,因此我们不需要直接调用它.的n'的确是同n'vreplicate_fix的,但现在看来似乎我们并不需要明确地提到它.它为什么传入?如果我们写出我们的类型,那就很明显了:

fun (n' : nat) (v : vec A n') => vcons a v : vec A (S n')
Run Code Online (Sandbox Code Playgroud)

我们需要n'这样,我们知道什么类型v,结果是什么类型的结果.

让我们看看这些功能在起作用:

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 *)
Run Code Online (Sandbox Code Playgroud)

事实上,他们是一样的:

(* 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.
Run Code Online (Sandbox Code Playgroud)

在上面,我提出重新实现nat_rect和朋友的运动.这是答案:

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.
Run Code Online (Sandbox Code Playgroud)

这希望清楚只是如何 nat_rect抽象递归模式,以及它为什么足够一般.