我发现自己一遍又一遍地重复一个模式,我想把它抽象出来。我相当有信心 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 *)
例如,要查找自由变量,我需要在变量案例和绑定案例中做一些有趣的事情,但对于其他所有事情,我只是递归地找到子表达式的所有自由变量,然后将这些列表合并在一起。对于生成所有约束列表的函数也是如此。替换情况有点棘手,因为三个函数的结果类型不同,用于组合子表达式的构造函数也不同:
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.
这个想法的关键是能够通常将构造函数应用于一定数量的参数,并拥有某种保留参数类型和数量的“映射”。
显然这个伪代码不起作用,因为 _ 的情况是不正确的。所以我的问题是,是否可以编写以这种方式组织的代码,还是我注定只能手动列出所有无聊的案例?