Coq/Proof 中的类似 Agda 的编程一般?

Agda-like programming in Coq/Proof General?

与 Agda 不同,Coq 倾向于将证明与函数分开。 Coq 给出的策略非常适合编写证明,但我想知道是否有一种方法可以复制一些 Agda 模式的功能。

具体来说,我想要:

在 CoqIde 或 Proof General 中这可能吗?

正如 ejgallego 在评论中所建议的那样,您(几乎)可以做到。有 company-coq 工具,它在 ProofGeneral 之上运行。

让我演示如何使用 company-coq 和 refine 策略来实现 map 功能。从

开始
Fixpoint map {A B} (f : A -> B) (xs : list A) : list B.

键入 refine ().,然后将光标放在括号内并键入 C-c C-a RET list RET -- 它会插入一个match 列表上的表达式有您手动填写的漏洞(让我们填写列表名称和基本情况)。

Fixpoint map {A B} (f : A -> B) (xs : list A) : list B.
  refine (match xs with
          | nil => nil
          | cons x x0 => cons _ _
          end).

为了完成它,我们将 x0 重命名为 tl 并提供递归案例 exact (map A B f tl).:

Fixpoint map {A B} (f : A -> B) (xs : list A) : list B.
  refine (match xs with
          | nil => nil
          | cons x tl => cons _ _
          end).
  exact (f x).
  exact (map A B f tl).
Defined.

还有一个有用的键盘快捷键 C-c C-a C-x 这有助于将当前目标提取到单独的 lemma/helper函数。

让我教你一个奇怪的技巧。它可能无法解决您所有的问题,但至少在概念上可能有所帮助。

让我们实现自然数的加法,后者由

给出
Inductive nat : Set :=
  | zero : nat
  | suc : nat -> nat.

你可以尝试写加法,但是出现了这种情况。

Theorem plus' : nat -> nat -> nat.
Proof.
  induction 1.

plus' < 2 subgoals

  ============================
   nat -> nat

subgoal 2 is:
 nat -> nat

你看不到你在做什么。

诀窍是更仔细地审视自己在做什么。我们可以引入一个无偿依赖类型,克隆 nat.

Inductive PLUS (x y : nat) : Set :=
  | defPLUS : nat -> PLUS x y.

想法是 PLUS x y 是 "the way to compute plus x y" 的类型。我们需要一个投影,使我们能够提取此类计算的结果。

Theorem usePLUS : forall x y, PLUS x y -> nat.
Proof.
  induction 1.
    exact n.
Defined.

现在我们准备好通过证明来编程了。

Theorem mkPLUS : forall x y, PLUS x y.
Proof.

mkPLUS < 1 subgoal

  ============================
   forall x y : nat, PLUS x y

进球的结论向我们展示了我们目前的 left-hand 方面和背景。 C-c C-c 在 Agda 中的类比是...

  induction x.

mkPLUS < 2 subgoals

  ============================
   forall y : nat, PLUS zero y

subgoal 2 is:
 forall y : nat, PLUS (suc x) y

你可以看到它已经完成了案例拆分!让我们取消基本案例。

    intros y.
      exact (defPLUS zero y    y).

调用PLUS的构造函数就像写一个等式。想象一下在它的第三个参数之前有一个 = 符号。对于step case,我们需要进行递归调用。

    intros y.
      eapply (fun h => (defPLUS (suc x) y    (suc (usePLUS x y h)))).

为了进行递归调用,我们用我们想要的参数调用usePLUS,这里是xy,但是我们抽象了第三个参数,这是对实际上如何计算它。我们只剩下那个子目标,实际上是终止检查。

mkPLUS < 1 subgoal

  x : nat
  IHx : forall y : nat, PLUS x y
  y : nat
  ============================
   PLUS x y

现在,您不使用 Coq 的防护检查,而是使用...

        auto.

...检查归纳假设是否涵盖递归调用。我们

Defined.

我们有一个工人,但我们需要一个包装器。

Theorem plus : nat -> nat -> nat.
Proof.
  intros x y.
    exact (usePLUS x y (mkPLUS x y)).
Defined.

我们准备好了。

Eval compute in (plus (suc (suc zero)) (suc (suc zero))).

Coq <      = suc (suc (suc (suc zero)))
     : nat

一个交互式构建工具。您 可以 玩弄它,通过使类型更具信息性来向您展示您正在解决的问题的相关细节。生成的证明脚本...

Theorem mkPLUS : forall x y, PLUS x y.
Proof.
  induction x.
    intros y.
      exact             (defPLUS zero    y    y).
    intros y.
      eapply (fun h =>  (defPLUS (suc x) y    (suc (usePLUS x y h)))).
        auto.
Defined.

...明确说明了它构建的程序。你可以看到这是定义加法。

如果您将此设置自动化以构建程序,然后在显示您正在构建的程序和关键 problem-simplifying 策略的界面上分层,您会得到一种有趣的小型编程语言,称为 Epigram 1。