Ed Kmett的递归方案包中的Fix,Mu和Nu有什么区别

hgi*_*sel 22 haskell recursive-datastructures recursion-schemes fixpoint-combinators

在Ed Kmett的recursion-scheme包中,有三个声明:

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
Run Code Online (Sandbox Code Playgroud)

这三种数据类型有什么区别?

sha*_*haf 25

Mu表示递归类型作为其折叠,并将其Nu表示为展开.在Haskell中,这些是同构的,并且是表示相同类型的不同方式.如果你假装Haskell没有任意递归,这些类型之间的区别变得更有趣:Mu f是固定的点最少(初始)f,并且Nu f是其最大的(终端)的固定点.

固定点fTT和之间的同构类型f T,即一对反函数in :: f T -> T,out :: T -> f T.该类型Fix只使用Haskell的内置类型递归来直接声明同构.但是你可以在/出两个执行MuNu.

举一个具体的例子,假装你不能写递归值.居民Mu Maybe,即价值观:: forall r. (Maybe r -> r) -> r,是自然,{0,1,2,...}; 居民Nu Maybe,即价值观:: exists x. (x, x -> Maybe x),是fatural的{0,1,2,...,∞}.想想这些类型的可能值,看看为什么Nu Maybe有一个额外的居民.

如果你想对这些类型有一些直觉,那么在没有递归的情况下实现以下内容可能是一种有趣的练习(大致按难度递增顺序):

  • 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 (Mu f) -> Mu f, outMu :: Mu f -> f (Mu f)
  • 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很难; 就像在逆流而行.

(有一点,我的意思是写出这些类型的更详细的解释,但这种格式可能有点太长了.)

  • 很好的解释,谢谢!是否有更详细的解释来源(文章/论文/书籍)?例如,术语级别固定点的类似物以及为什么 Nu(作为递归类型作为其折叠的表示)是类型级别的最不固定点。LFP 和初始代数之间以及 GFP 和终端代数之间也有重要的联系。 (2认同)
  • Sergey Cherepanov:我不知道有哪一个,不过[这里](http://web.archive.org/web/20150922131653/http://sneezy.cs.nott.ac.uk/fplunch/weblog /?p=83) 是使用参数性的初始化证明。初始代数和初始不动点之间的联系有时称为兰贝克引理。B. Mehta:与 id :: forall a 相同。a -> a 无法区分不同的输入,外界无法区分 Nu Just [] 和 Nu Just 0。 paulotorrens:至少是这种类型的惯用名称,自然数的一点紧致化,为此原因。 (2认同)
  • 感谢有趣的锻炼!如果有人感兴趣,我在这里上传了我的答案:https://gist.github.com/inamiy/7488380da6001cb0f778b59d9f7230cd (2认同)