Haskell 在Ed Kmett';s递归方案包
在Ed Kmett的Haskell 在Ed Kmett';s递归方案包,haskell,recursive-datastructures,recursion-schemes,fixpoint-combinators,Haskell,Recursive Datastructures,Recursion Schemes,Fixpoint Combinators,在Ed Kmett的递归方案包中,有三个声明: newtype Fix f = Fix (f (Fix f)) newtype Mu f = Mu (forall a. (f a -> a) -> a) data Nu f where Nu :: (a -> f a) -> a -> Nu f 这三种数据类型之间的区别是什么?Mu将递归类型表示为其折叠,而Nu将其表示为其展开。在Haskell中,这些是同构的,并且是表示同一类型的不同方式。如果假设Has
递归方案
包中,有三个声明:
newtype Fix f = Fix (f (Fix f))
newtype Mu f = Mu (forall a. (f a -> a) -> a)
data Nu f where
Nu :: (a -> f a) -> a -> Nu f
这三种数据类型之间的区别是什么?
Mu
将递归类型表示为其折叠,而Nu
将其表示为其展开。在Haskell中,这些是同构的,并且是表示同一类型的不同方式。如果假设Haskell没有任意递归,那么这些类型之间的区别就变得更有趣了:Mu f
是f
的最小(初始)不动点,Nu f
是其最大(终端)不动点
f
的不动点是T
类型,是T
和ft
之间的同构,即一对逆函数in::ft->T
,out::T->ft
。类型Fix
只是使用Haskell内置的类型递归直接声明同构。但是您可以为Mu
和Nu
实现in/out
举个具体的例子,假设您不能编写递归值。Mu的居民可能
,即价值观::对于所有r。(可能是r->r)->r
,是自然的,{0,1,2,…};Nu的居民可能
,即值::存在于x。(x,x->Maybe x)
,是自然数{0,1,2。。。,∞}. 想想这些类型的可能值,看看为什么Nu可能有一个额外的居住者
如果您想对这些类型有一些直觉,那么在不递归的情况下实现以下内容(大致按照难度的增加顺序)可能是一个有趣的练习:
zeroMu::Mu Maybe
,succMu::Mu Maybe->Mu Maybe
zeroNu::Nu Maybe
,succNu::Nu Maybe->Nu Maybe
,inftyNu::Nu Maybe
muTofix::Mu f->Fix f
,fixToNu::Fix f->Nu f
inMu::f(muf)->muf
,outMu::muf->f(muf)
inNu::f(Nu-f)->Nu-f
,outNu::Nu-f->f(Nu-f)
您也可以尝试实现这些,但它们需要递归:
nuToFix::Nu f->Fix f
,fixToMu::Fix f->Mu f
Mu f
是最小的固定点,而Nu f
是最大的固定点,因此编写函数::Mu f->Nu f
非常容易,但编写函数::Nu f->Mu f
很难,就像逆流而上一样
(有一次我想对这些类型写一个更详细的解释,但对于这种格式来说可能有点太长了。)我不知道太多的理论,但我认为对于更多的证明语言,Mu
是最小的不动点,Nu
是最大的不动点。在Haskell中,这三者应该是等价的(我相信)。请注意,对于Mu
和Nu
来说,实现cata
和ana
非常容易。尝试解决这个问题是一个很好的解释,谢谢!是否有更详细的来源(文章/论文/书籍)来解释它?例如,术语级修复点的类比以及为什么Nu(作为其折叠的递归类型的表示)是类型级别上的最小不动点。LFP和初始代数之间以及GFP和终端余代数之间也存在着重要的联系。我肯定我遗漏了一些东西,但Nu可能有更多的居民-至少有很多人喜欢Haskell类型检查。例如,Nu Just[]
,Nu Just“abcd”
,Nu(无常数)42
似乎所有的类型都是正确的。我错了什么?等等。自然数?这是一件事吗?谢尔盖·切雷帕诺夫:我不知道一件事,不过这是用参数证明初始性的。初始代数和初始不动点之间的联系有时被称为兰贝克引理。B.梅塔:与id::for的方式相同所有a.a->a都无法区分不同的输入,外部世界无法区分Nu Just[]和Nu Just 0。paulotorrens:这至少是这种类型的惯用名称,自然的一点压缩,因此。感谢有趣的练习!如果有人感兴趣,我将我的答案上传到这里: