感应式携带证明

时间:2018-12-14 21:19:46

标签: coq logical-foundations

“软件基础”中有一个练习,我已经尝试正确地解决了一段时间,但是实际上在尝试写下所需的功能方面已经遇到了麻烦。这是练习的相关部分

  

考虑自然数的不同,更有效的表示   使用二进制而不是一元制。也就是说,不用说   每个自然数要么是零,要么是自然数的后继   数字,我们可以说每个二进制数字都是

     
      
  • 零,
  •   
  • 将二进制数两次,或者
  •   
  • 一个二进制数的两倍多。
  •   
     

(a)首先,写出对应于bin类型的归纳定义   对此二进制数字的描述。

幼稚的定义不太有效,因为您最终可以构造将1加到已经加1的数字上或将0乘以2的术语,这没有充分的理由。为了避免那些我想过的事情,我会将某种状态转换编码到构造函数中来避免这些情况,但这有点棘手,所以这是我能想到的最好的方法

Inductive tag : Type := z | nz | m. (* zero | nonzero | multiply by 2 *)

Inductive bin_nat : tag -> Type :=
  (* zero *)
  | Zero : bin_nat z
  (* nonzero *)
  | One : bin_nat nz
  (* multiply by 2 -> nonzero *)
  | PlusOne : bin_nat m -> bin_nat nz
  (* nonzero | multiply by 2 -> multiply by 2 *)
  | Multiply : forall {t : tag}, (t = m \/ t = nz) -> bin_nat t -> bin_nat m.

使用上面的表示,我避免了没有意义的术语问题,但是现在我必须乘以2来携带证明。但是我实际上不知道如何在递归函数中使用这些东西。

我知道如何构造证明和术语,它们看起来像这样

(* nonzero *)
Definition binr (t : tag) := or_intror (t = m) (eq_refl nz).
(* multiply by 2 *)
Definition binl (t : tag) := or_introl (t = nz) (eq_refl tag m).
(* some terms *)
Check Zero.
Check One.
Check (Multiply (binr _) One).
Check (Multiply (binl _) (Multiply (binr _) One)).
Check PlusOne (Multiply (binl _) (Multiply (binr _) One)).

我还可以写下要与函数相对应的定理的“证明”,但我不知道如何将其实际转换为函数。这是转换函数的证明

Definition binary_to_nat : forall t : tag, bin_nat t -> nat.
Proof.
intros.
einduction H as [ | | b | t proof b ].
  { exact 0. } (* Zero *)
  { exact 1. } (* One *)
  { exact (S (IHb b)). } (* PlusOne *)
  { (* Multiply *)
    edestruct t.
    cut False.
    intros F.
    case F.
    case proof.
    intros F.
    inversion F.
    intros F.
    inversion F.
    exact (2 * (IHb b)).
    exact (2 * (IHb b)).
  }
Defined.

我知道这个术语是我想要的函数,因为我可以验证使用它进行计算时得到正确的答案

Section Examples.

Example a : binary_to_nat z Zero = 0.
Proof.
lazy.
trivial.
Qed.

Example b : binary_to_nat nz One = 1.
Proof.
lazy.
trivial.
Qed.

Example c : binary_to_nat m ((Multiply (binl _) (Multiply (binr _) One))) = 4.
Proof.
lazy.
trivial.
Qed.

End Examples.

所以最后一个问题是,有没有一种简单的方法可以将上述证明项以简单的方式转换为实际函数,还是我必须尝试对证明项进行逆向工程?

1 个答案:

答案 0 :(得分:3)

我喜欢您使用状态和索引归纳类型表示有效二进制数的想法。但是,正如问题所表明的那样,有可能只使用归纳类型的三个构造函数即可:零,乘以2,乘以2并加一。这意味着我们唯一需要避免的过渡是将零乘以2。

Inductive tag : Type := z | nz. (* zero | nonzero *)

Inductive bin_nat : tag -> Type :=
  (* zero *)
  | Zero : bin_nat z
  (* multiply by 2 *)
  | TimesTwo : bin_nat nz -> bin_nat nz
  (* multiply by 2 and add one *)
  | TimesTwoPlusOne : forall {t : tag}, bin_nat t -> bin_nat nz.

然后,例如,

Let One := TimesTwoPlusOne Zero. (* 1 *)
Let Two := TimesTwo One. (* 10 *)
Let Three := TimesTwoPlusOne One. (* 11 *)
Let Four := TimesTwo Two. (* 100 *)

因此TimesTwo在二进制表示的末尾添加一个零,而TimesTwoPlusOne在二进制表示的末尾添加一个。

如果您想自己尝试一下,就不要再看了。

(我会把它放在spoiler标签中,但显然spoiler标签中的代码块有问题)

递增二进制数。

Fixpoint bin_incr {t: tag} (n: bin_nat t): bin_nat nz :=
match n with
| Zero => One
| TimesTwo n' => TimesTwoPlusOne n'
| @TimesTwoPlusOne _ n' => TimesTwo (bin_incr n')
end.

用于将nat转换为二进制文件的帮助程序定义。

Definition nat_tag (n: nat): tag :=
match n with
| 0 => z
| S _ => nz
end.

nat转换为二进制。

Fixpoint nat_to_bin (n: nat): bin_nat (nat_tag n) :=
match n with
| 0 => Zero
| S n' => bin_incr (nat_to_bin n')
end.

将二进制转换为nat。请注意,这使用符号进行自然数的乘法和加法。如果这不起作用,则可能没有打开正确的作用域。

Fixpoint bin_to_nat {t: tag} (n: bin_nat t): nat :=
match n with
| Zero => 0
| TimesTwo n' => 2 * (bin_to_nat n')
| @TimesTwoPlusOne _ n' => 1 + 2 * (bin_to_nat n')
end.

我们从这些定义中获得了实际的功能(注意20是二进制的10100)。

Compute nat_to_bin 20.
= TimesTwo
     (TimesTwo (TimesTwoPlusOne (TimesTwo (TimesTwoPlusOne Zero))))
 : bin_nat (nat_tag 20)

Compute bin_to_nat (nat_to_bin 20).
= 20
 : nat

进一步的技术说明。我在Coq的两个版本(8.6和8.9 + alpha)上使用了此代码,一个版本要求在TimesTwoPlusOne上进行匹配时显式地放入标记,而另一个版本则允许它保持隐式。上面的代码在任何情况下都应该起作用。