按照this的方法,我正在尝试基于Haskell中的实现,使用Coq中的效果处理程序对功能程序进行建模。本文提出了两种方法:
data Prog sig a = Return a | Op (sig (Prog sig a))
由于终止检查不喜欢非严格的正定义,因此无法直接定义此数据类型。但是,如this paper中所述,容器可用于表示严格为正的函子。这种方法有效,但是由于我需要对需要显式作用域语法的作用域建模,因此可能会出现不匹配的开始/结束标签。对于程序的推理,这并不理想。
data Prog sig a = Return a | Op (sig (Prog sig) a)
现在sig的类型为(*-> *)-> *-> *。出于与以前相同的原因,无法在Coq中定义数据类型。我正在寻找对这种数据类型进行建模的方法,以便可以在没有显式作用域标记的情况下实现作用域效果。
我为高阶函子定义容器的尝试并未取得成果,并且我找不到关于此主题的任何信息。我感谢正确方向的指点和有用的评论。
编辑:我要代表的范围语法的一个示例是以下异常数据类型。
data HExc e m a = Throw′ e | forall x. Catch′ (m x) (e -> m x) (x -> m a)
Edit2:我已经将建议的想法与我的方法合并了。
Inductive Ext Shape (Pos : Shape -> Type -> Type -> Type) (F : Type -> Type) A :=
ext : forall s, (forall X, Pos s A X -> F X) -> Ext Shape Pos F A.
Class HContainer (H : (Type -> Type) -> Type -> Type):=
{
Shape : Type;
Pos : Shape -> Type -> Type -> Type;
to : forall M A, Ext Shape Pos M A -> H M A;
from : forall M A, H M A -> Ext Shape Pos M A;
to_from : forall M A (fx : H M A), @to M A (@from M A fx) = fx;
from_to : forall M A (e : Ext Shape Pos M A), @from M A (@to M A e) = e
}.
Section Free.
Variable H : (Type -> Type) -> Type -> Type.
Inductive Free (HC__F : HContainer H) A :=
| pure : A -> Free HC__F A
| impure : Ext Shape Pos (Free HC__F) A -> Free HC__F A.
End Free.
可以找到代码here。 Lambda Calculus示例有效,我可以证明容器表示形式与数据类型同构。 对于异常处理程序数据类型的简化版本,我尝试了相同的操作,但它不适合容器表示形式。
定义智能构造函数也不起作用。在Haskell中,构造函数通过将Catch'
应用于可能发生异常和继续的程序,该程序在开始时为空。
catch :: (HExc <: sig) => Prog sig a -> Prog sig a
catch p = inject (Catch' p return)
我在Coq实现中看到的主要问题是形状需要通过函子进行参数化,这会导致各种问题。
答案 0 :(得分:3)
这个答案比我以前的答案给出了更多关于如何从函子派生容器的直觉。我采取了一个完全不同的角度,所以我在提出一个新的答案,而不是修改旧的答案。
让我们首先考虑一个简单的递归类型,以理解非参数容器,并与参数化泛化进行比较。不关心范围的Lambda演算由以下函子给出:
Inductive LC_F (t : Type) : Type :=
| App : t -> t -> LC_F t
| Lam : t -> LC_F t
.
从这种类型中我们可以学习到两条信息:
shape 告诉我们有关构造函数(App
,Lam
)的信息,以及可能与语法的递归性质无关的辅助数据(无)这里)。有两个构造函数,因此形状具有两个值。 Shape := App_S | Lam_S
(bool
也可以,但是将形状声明为独立的归纳类型很便宜,而且命名构造函数也可以作为文档使用。)
对于每种形状(即构造函数), position 告诉我们该构造函数中语法的递归出现。 App
包含两个子项,因此我们可以将它们的两个位置定义为布尔值; Lam
包含一个子项,因此其位置是一个单位。也可以将Pos (s : Shape)
设置为索引归纳类型,但这是使用(只需尝试)进行编程的痛苦。
(* Lambda calculus *)
Inductive ShapeLC :=
| App_S (* The shape App _ _ *)
| Lam_S (* The shape Lam _ *)
.
Definition PosLC s :=
match s with
| App_S => bool
| Lam_S => unit
end.
现在,适当范围的lambda演算:
Inductive LC_F (f : Type -> Type) (a : Type) : Type :=
| App : f a -> f a -> LC_F a
| Lam : f (unit + a) -> LC_F a
.
在这种情况下,我们仍然可以重用之前的Shape
和Pos
数据。但是此函子编码了另一条信息:每个位置如何更改类型参数a
。我将此参数称为上下文(Ctx
)。
Definition CtxLC (s : ShapeLC) : PosLC s -> Type -> Type :=
match s with
| App_S => fun _ a => a (* subterms of App reuse the same context *)
| Lam_S => fun _ a => unit + a (* Lam introduces one variable in the context of its subterm *)
end.
此容器(ShapeLC, PosLC, CtxLC)
与函子LC_F
通过同构关系:在sigma { s : ShapeLC & forall p : PosLC s, f (CtxLC s p a) }
和LC_F a
之间。特别是,请注意函数y : forall p, f (CtxLC s p)
如何准确地告诉您如何填充形状s = App_S
或s = Lam_S
以构造值App (y true) (y false) : LC_F a
或Lam (y tt) : LC_F a
。
我以前的表示形式在Ctx
的某些其他类型索引中编码了Pos
。这些表示是等效的,但是这里看起来比较整洁。
我们将仅考虑Catch
构造函数。它具有四个字段:类型X
,主计算(返回X
),异常处理程序(也恢复X
)和延续(消耗{{ 1}})。
X
形状是单个构造函数,但是您必须包含Inductive Exc_F (E : Type) (F : Type -> Type) (A : Type) :=
| ccatch : forall X, F X -> (E -> F X) -> (X -> F A) -> Exc_F E F A.
。本质上,查看所有字段(可能会展开嵌套的归纳类型),并保留所有未提及X
的数据,这就是您的形状。
F
位置类型列出了从相应形状的Inductive ShapeExc :=
| ccatch_S (X : Type) (* The shape ccatch X _ (fun e => _) (fun x => _) *)
.
(* equivalently, Definition ShapeExc := Type. *)
中提取F
的所有方法。特别是,位置包含要与之一起使用函数的参数,并且可能包含任何数据来解析任何其他种类的分支。特别是,您需要了解异常类型以存储处理程序的异常。
Exc_F
最后,对于每个职位,您都需要确定上下文的变化方式,即现在要计算Inductive PosExc (E : Type) (s : ShapeExc) : Type :=
| main_pos (* F X *)
| handle_pos (e : E) (* E -> F X *)
| continue_pos (x : getX s) (* X -> F A *)
.
(* The function getX takes the type X contained in a ShapeExc value, by pattern-matching: getX (ccatch_S X) := X. *)
还是X
:
A
使用your code中的约定,然后可以对Definition Ctx (E : Type) (s : ShapeExc) (p : PosExc E s) : Type -> Type :=
match p with
| main_pos | handle_pos _ => fun _ => getX s
| continue_pos _ => fun A => A
end.
构造函数进行如下编码:
Catch
完整要旨https://gist.github.com/Lysxia/6e7fb880c14207eda5fc6a5c06ef3522
答案 1 :(得分:2)
“一阶”免费monad编码的主要技巧是将函子F : Type -> Type
编码为容器,该函子本质上是一个从属对{ Shape : Type ; Pos : Shape -> Type }
,因此对于所有{{1 }},类型a
与sigma类型F a
同构。
更进一步,我们可以将高阶函子{ s : Shape & Pos s -> a }
编码为容器F : (Type -> Type) -> (Type -> Type)
,这样,对于所有{ Shape : Type & Pos : Shape -> Type -> (Type -> Type) }
和f
,{{1 }}与a
同构。
我不太了解F f a
中多余的{ s : Shape & forall x : Type, Pos s a x -> f x }
参数在做什么,但是它有效,至少可以在结果类型中构造一些lambda演算项
例如,lambda演算语法函数:
Type
由定义为:
的容器Pos
表示
Inductive LC_F (f : Type -> Type) (a : Type) : Type :=
| App : f a -> f a -> LC_F a
| Lam : f (unit + a) -> LC_F a
.
其中(Shape, Pos)
和(* LC container *)
Shape : Type := bool; (* Two values in bool = two constructors in LC_F *)
Pos (b : bool) : Type -> (Type -> Type) :=
match b with
| true => App_F
| false => Lam_F
end;
的表达式为:
App_F
然后,像这样的自由类单子Lam_F
(由Inductive App_F (a : Type) : TyCon :=
| App_ (b : bool) : App_F a a
.
Inductive Lam_F (a : Type) : TyCon :=
| Lam_ : Lam_F a (unit + a)
.
隐式参数化)由以下公式给出:
Prog
已经定义了一些样板,您可以编写以下示例:
(Shape, Pos)
要点:https://gist.github.com/Lysxia/5485709c4594b836113736626070f488