Haskell 在存在类型运算符的情况下实现嵌套/递归数据类型

Haskell 在存在类型运算符的情况下实现嵌套/递归数据类型,haskell,types,lambda-calculus,higher-kinded-types,type-theory,Haskell,Types,Lambda Calculus,Higher Kinded Types,Type Theory,我一直在按照Pierce的类型和编程语言在Rust中实现System F-omega,我正在寻找使用iso recursivefold/unfold操作符向我的实现中添加递归类型的指导。我已经添加了sum、product、record、existential和universal类型 在SystemF(w/o类型运算符)中,我发现这相对简单,我只是让访问者在解析后通过AST,在案例分析或类型构造期间执行折叠/展开同构 对于System F omega,由于在类型级别存在lambda抽象,因此情况稍

我一直在按照Pierce的类型和编程语言在Rust中实现System F-omega,我正在寻找使用iso recursive
fold
/
unfold
操作符向我的实现中添加递归类型的指导。我已经添加了sum、product、record、existential和universal类型

在SystemF(w/o类型运算符)中,我发现这相对简单,我只是让访问者在解析后通过AST,在案例分析或类型构造期间执行折叠/展开同构

对于System F omega,由于在类型级别存在lambda抽象,因此情况稍微复杂一些。例如(使用标准的lambda-calc/haskell-ish语法),让我们假设我想要定义一个参数化的
列表
数据类型

datatype List a = Cons a (List a) | Nil
这可以在我们的结石中编码为(省略*种):

但是,当我们试图展开值时,这就有问题了,因为引入了一个新的抽象,试图再次绑定
a
。这似乎是可行的,只要我们将A型混凝土储存在某个地方

或者我们可以将其“lambda”放到:

type ListF = ΛX. ΛA. Cons (A, X) | Nil
type List  = ΛA. μ (ListF A)
lambda删除对于这样的简单递归类型非常有效,而且实现起来也很简单,我可以设想一种自动生成
ListF
类型的方法。但我有一种烦躁的感觉,这件事有点不对劲

我一直试图阅读有关这方面的文献(Mendler风格的迭代,Andreas Abel,Ralph Matthes,Tarmo Uustalu,“高阶和嵌套数据类型的迭代和协同迭代方案”,等等),但其中一些内容有点让我不知所措,我不知道如何以具体的方式实际实现它。如有任何建议,将不胜感激

List = μ ΛX. ΛA. Cons A (X A) | Nil
-- μF unfolds to F(μF)
= (ΛX. ΛA. Cons A (X A) | Nil) (μ ΛX. ΛA. Cons A (X A) | Nil)
-- substitute *correctly*
= ΛA. Cons A ((μ ΛX. ΛA. Cons A (X A) | Nil) A) | Nil
--                                           ^ you dropped this!
-- folding back
List = ΛA. Cons A (List A) | Nil
A
不需要“存储”在类型本身以外的任何位置。您在不应删除包含它的应用程序时删除了它。注意你的展开是不友善的。顶级
Cons
的第二个字段具有种类
*=>*
,但这是不对的。但是原作的种类很好,所以展开(应该保留种类)一定出了问题

“Lambda下降”很好,但并不总是可能的。考虑

data Binary a = Leaf a | Branch (Binary (a, a))
对于某些自然的
n
,它精确地包含
2^n
a
s。这种类型是不规则递归的,不能用
μ::(*=>*)=>*
表示为
List
can

type Binary = μ ΛX. ΛA. Leaf A | Branch (X (A, A))

我不确定我是否看到了mu的抽象化问题。在第一个示例中,
列表
应展开为
Cons A((μ…)A)| Nil
,而不是
Cons A(μ…)Nil
(即μ应应用于A)。这可能是混乱的根源吗?这无疑会使类型检查变得困难,但这似乎不是你关心的问题,是吗?当我从代码中翻译它时,这只是一个拼写错误,谢谢你抓住了它。啊,这是一个拼写错误,谢谢你抓住了它。我保证我理解替换是如何工作的,并让它在代码中工作:)。即使有正确的替换,它也不是格式良好的,因为您不能将递归类型应用于具体类型,而只能应用于类型抽象。让我们将
Nat
应用到正确替换的
List
类型:
List=Cons-Nat((μ∧X.A.Cons-A(xa)| Nil)Nat)| Nil
。Nat不能替换为A的内部出现,因此我们必须将
Nat
存储在某个地方,以便下次我们需要折叠时应用它。我相信,即使您要修改键入规则,以便在类型检查期间执行展开同构(以便您可以计算
(μ∧X.A.Cons A(X A))| Nil)Nat
),您将在一个无限循环中结束,因为您正在不断引入一个新的类型抽象
λa.
。这不正确吗?为什么需要在中替换
Nat
?只需离开应用程序,让折叠操作符的类型来处理它<代码>fold1_F_A::F(μF)A->μF A,特别是,
fold1_(λX.A.Cons A(xa)Nil):(Cons Nat((μ∧X.A.Cons A(xa)Nil)Nat)->(μ∧X.Cons A(xa)Nil)Nat)
。我原以为那是另一条路线。Andreas Abel的博士论文“高阶类型的多态Lambda演算”也在一定程度上涵盖了这一点,第3.5节和第4节。我认为你描述的路线(以及diss.也描述的路线)是我最终可能使用的路线。
type Binary = μ ΛX. ΛA. Leaf A | Branch (X (A, A))