fold - coq 中归纳数据类型的广义折叠

标签 fold coq

我发现自己一遍又一遍地重复一个模式,我想把它抽象出来。我相当有信心 coq 具有足够的表现力来捕捉模式,但我在弄清楚如何做到这一点时遇到了一些麻烦。我正在定义一种编程语言,它具有表示句法术语的相互递归归纳数据类型:

Inductive Expr : Set :=
  | eLambda  (x:TermVar) (e:Expr)
  | eVar     (x:TermVar)
  | eAscribe (e:Expr)  (t:IFType)
  | ePlus    (e1:Expr) (e2:Expr)

  | ... many other forms ...

with DType : Set :=
  | tArrow (x:TermVar) (t:DType) (c:Constraint) (t':DType)
  | tInt

  | ... many other forms ...

with Constraint : Set :=
  | cEq (e1:Expr) (e2:Expr)
  | ...

现在,我需要为这些类型定义许多函数。例如,我想要一个查找所有自由变量的函数、一个执行替换的函数以及一个提取所有约束集的函数。这些函数都具有以下形式:
Fixpoint doExpr (e:Expr) := match e with
  (* one or two Interesting cases *)
  | ...

  (* lots and lots of boring cases,
  ** all of which just recurse on the subterms
  ** and then combine the results in the same way
  *)
  | ....

with doIFType (t:IFType) := match t with
  (* same structure as above *)

with doConstraint (c:Constraint) := match c with
  (* ditto *)

例如,要查找自由变量,我需要在变量案例和绑定(bind)案例中做一些有趣的事情,但对于其他所有事情,我只是递归地找到子表达式的所有自由变量,然后将这些列表合并在一起。对于生成所有约束列表的函数也是如此。替换情况有点棘手,因为三个函数的结果类型不同,用于组合子表达式的构造函数也不同:
Variable x:TermVar, v:Expr.
Fixpoint substInExpr (e:Expr) : **Expr** := match e with
  (* interesting cases *)
  | eLambda y e' =>
      if x = y then eLambda y e' else eLambda y (substInExpr e')
  | eVar y =>
      if x = y then v else y

  (* boring cases *)
  | eAscribe e' t  => **eAscribe** (substInExpr e') (substInType t)
  | ePlus    e1 e2 => **ePlus**    (substInExpr e1) (substInExpr e2)
  | ...

with substInType       (t:Type)       : **Type** := match t with ...
with substInConstraint (c:Constraint) : **Constraint** := ...
.

编写这些函数既乏味又容易出错,因为我必须为每个函数写出所有不感兴趣的情况,并且我需要确保对所有子项进行递归。我想写的是如下内容:
Fixpoint freeVars X:syntax := match X with
  | syntaxExpr eVar    x         => [x]
  | syntaxExpr eLambda x e       => remove x  (freeVars e)
  | syntaxType tArrow  x t1 c t2 => remove x  (freeVars t1)++(freeVars c)++(freeVars t2)
  | _          _       args      => fold (++) (map freeVars args)
end.

Variable x:TermVar, v:Expr.
Fixpoint subst X:syntax := match X with
  | syntaxExpr eVar y      => if y = x then v else eVar y
  | syntaxExpr eLambda y e => eLambda y (if y = x then e else (subst e))
  | syntaxType tArrow ...

  | _ cons args => cons (map subst args)
end.

这个想法的关键是能够通常将构造函数应用于一定数量的参数,并拥有某种保留参数类型和数量的“映射”。

显然,这个伪代码不起作用,因为 _ 的情况是不正确的。所以我的问题是,是否可以编写以这种方式组织的代码,还是我注定只能手动列出所有无聊的案例?

最佳答案

这是另一种方式,虽然它不是每个人的一杯茶。

这个想法是将递归从类型和评估器中移出,改为参数化它,并将您的表达式值转换为折叠。这在某些方面提供了便利,但在其他方面提供了更多的努力——这实际上是一个你最终花费最多时间的问题。好的方面是评估器可以很容易编写,并且您不必处理相互递归的定义。然而,一些在其他方面更简单的事情可能会成为这种风格的脑筋急转弯。

Require Import Ssreflect.ssreflect.
Require Import Ssreflect.ssrbool.
Require Import Ssreflect.eqtype.
Require Import Ssreflect.seq.
Require Import Ssreflect.ssrnat.

Inductive ExprF (d : (Type -> Type) -> Type -> Type)
                (c : Type -> Type) (e : Type) : Type :=
  | eLambda  (x:nat) (e':e)
  | eVar     (x:nat)
  | eAscribe (e':e)  (t:d c e)
  | ePlus    (e1:e) (e2:e).

Inductive DTypeF (c : Type -> Type) (e : Type) : Type :=
  | tArrow (x:nat) (t:e) (c':c e) (t':e)
  | tInt.

Inductive ConstraintF (e : Type) : Type :=
  | cEq (e1:e) (e2:e).

Definition Mu (f : Type -> Type) := forall a, (f a -> a) -> a.

Definition Constraint := Mu ConstraintF.
Definition DType      := Mu (DTypeF ConstraintF).
Definition Expr       := Mu (ExprF DTypeF ConstraintF).

Definition substInExpr (x:nat) (v:Expr) (e':Expr) : Expr := fun a phi =>
  e' a (fun e => match e return a with
    (* interesting cases *)
    | eLambda y e' =>
        if (x == y) then e' else phi e
    | eVar y =>
        if (x == y) then v _ phi else phi e

    (* boring cases *)
    | _ => phi e
    end).

Definition varNum (x:ExprF DTypeF ConstraintF nat) : nat :=
  match x with
  | eLambda _ e => e
  | eVar y => y
  | _ => 0
  end.

Compute (substInExpr 2 (fun a psi => psi (eVar _ _ _ 3))
                     (fun _ phi =>
                        phi (eLambda _ _ _ 1 (phi (eVar _ _ _ 2)))))
        nat varNum.

Compute (substInExpr 1 (fun a psi => psi (eVar _ _ _ 3))
                     (fun _ phi =>
                        phi (eLambda _ _ _ 1 (phi (eVar _ _ _ 2)))))
        nat varNum.

关于fold - coq 中归纳数据类型的广义折叠,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15190654/

相关文章:

loops - 在 Haskell 中融合多重折叠

c++ - 如何调用所有可变继承类的函数?

coq - 如何在 Coq 语句中证明给定的集合

WP 生成的 Coq 文件无法编译

coq - 如何使用 Coq 库中的引理?

coq - 如何在 Ltac 中尝试某种策略,但如果失败则继续

Haskell - 严格与非严格与 foldl

vim - 如何拉出没有折叠内容的代码块?

haskell - 模式匹配的严格性与解构性

functional-programming - Coq:证明n和(S n)的乘积是偶数