パターンを何度も繰り返していることに気づき、それを抽象化したいと思います。私は、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 *)
たとえば、自由変数を見つけるには、変数のケースとバインディングを行うケースで興味深いことを行う必要がありますが、それ以外の場合は、部分式の自由変数をすべて再帰的に見つけて、それらのリストを結合します。すべての制約のリストを生成する関数についても同様です。3 つの関数の結果の型が異なり、部分式を結合するために使用されるコンストラクターも異なるため、置換のケースはもう少しトリッキーです。
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.
このアイデアの鍵は、コンストラクターをいくつかの引数に一般的に適用する機能と、引数の型と数を保持するある種の「マップ」を持つ機能です。
_ のケースが正しくないため、明らかにこの疑似コードは機能しません。だから私の質問は、このように編成されたコードを書くことは可能ですか、それとも退屈なケースをすべて手動でリストアップする運命にあるのでしょうか?