共享与非共享定点组合器

时间:2018-12-11 00:45:16

标签: haskell fixed-point combinators y-combinator letrec

这是Haskell中定点组合器的通常定义:

fix :: (a -> a) -> a
fix f = let x = f x in x

https://wiki.haskell.org/Prime_numbers上,他们定义了不同的定点组合器:

_Y   :: (t -> t) -> t
_Y g = g (_Y g)                -- multistage, non-sharing,  g (g (g (g ...)))
    -- g (let x = g x in x)    -- two g stages, sharing
  

_Y是一个非共享定点组合器,这里安排了递归的“ telescoping” 多阶段素数生产(生产者的)。

这到底是什么意思?在这种情况下,什么是“共享”还是“不共享”? _Yfix有何区别?

3 个答案:

答案 0 :(得分:5)

“共享”是指f x重用它创建的x;但是使用_Y g = g . g . g . g . ...,每个g都会重新计算其输出(参见thisthis)。

在这种情况下,共享版本的内存使用情况更糟leads to a space leak 1

_Y的定义反映了通常的lambda演算定义对 Y 组合器的作用,该组合器通过复制模拟递归,而真正的递归指的是< em> same (因此, shared )实体。

    x      = f x
    (_Y g) = g (_Y g)
两个x都引用相同实体,但是每个(_Y g)都引用相同但独立的实体。无论如何,这就是它的目的。

当然,由于引用透明性, Haskell语言对此不做任何保证。但是 GHC编译器确实是这种方式。

_Y g是一个常见的子表达式,它可以由编译器“消除”,方法是为它赋予一个名称并重新使用该命名实体,从而颠覆了它的全部用途。这就是为什么GHC具有“没有公共子表达式消除” -fno-cse标志的原因,该标志可明确阻止此情况。过去,您必须使用此标志来实现所需的行为,但现在不再。 GHC在使用最新版本(阅读:现在已经有几年了)的情况下,不再会在消除常见子表达式方面表现得那么积极。

免责声明:我是您所引用页面的那部分的作者。希望能在Wiki页面上来回往返,但从未来过,所以我的工作没有得到这样的评价。要么没人打扰,要么 是可以通过的(没有重大错误)。 Wiki似乎已被大量废弃多年。


1 涉及的g函数

(3:) . minus [5,7..] . foldr (\ (x:xs) ⟶ (x:) . union xs) [] 
                      . map (\ p ⟶ [p², p² + 2p..])
给定所有奇质数的流,

生成所有奇质数的流。为了产生素数 N ,它至少消耗其输入流,直到其值大于 sqrt(N) 的第一个素数。因此,通过重复平方粗略地给出了生产点,并且在链中(或“塔” )中总共有{em> ~ log (log N) 个此类g函数>)这些 primes生产者中的每一个,它们都是立即可以垃圾回收的,最低的一个产生其primes的只是先验已知的第一个奇数质素 3

使用两阶段式_Y2 g = g x where { x = g x }时,链中将只有两个,但是只有前一个阶段会立即被垃圾回收,如上面引用的链接所述

答案 1 :(得分:4)

_Y转换为以下STG:

_Y f = let x = _Y f in f x

fix的翻译方式与Haskell来源相同:

fix f = let x = f x in x

所以fix f设置了一个递归的循环x并返回它,而_Y是一个递归函数,重要的是它不是尾递归。强制_Y f进入f,将一个 new 调用作为参数传递给_Y f,因此每个递归调用都将建立一个 new thunk ;强制x返回的fix f进入f,将x本身作为参数传递,因此每个递归调用都属于同一个重击—这就是“共享”的含义

通常,共享版本 具有更好的内存使用率,还可以让GHC RTS检测到某些无限循环。强制重击时,在评估开始之前,将其替换为“黑洞”;如果在评估一个重击过程中的任何时候都从同一线程到达一个黑洞,那么我们知道我们有一个无限循环,并且可能引发异常(您可能会看到显示为Exception: <<loop>>)。

答案 2 :(得分:2)

从GHC / Haskell的角度来看,我认为您已经收到了很好的答案。我只是想输入一些历史/理论注释。

在长谷川的博士论文https://www.springer.com/us/book/9781447112211

中,对递归的展开视图与循环视图之间的对应关系进行了严格的研究。

(这是一篇较短的论文,您无需付费就可以阅读Springer:https://link.springer.com/content/pdf/10.1007%2F3-540-62688-3_37.pdf

长谷川假设一个追溯的单曲面类,这一要求比领域理论中通常的PCPO假设严格得多,后者构成了我们通常如何思考Haskell的基础。长谷川展示的是,人们可以在这种情况下定义这些“共享”不动点算子,并确定它们对应于丘奇(Church)的λ演算中不动点的通常展开视图。也就是说,无法通过使它们产生不同的答案来区分它们。

长谷川的对应符合所谓的中心箭头;即,当不涉及“效果”时。后来,Benton和Hyland扩展了这项工作,并指出了在某些情况下,对应的箭头在下面的箭头也可以执行“轻微”单峰效果的情况下成立:https://pdfs.semanticscholar.org/7b5c/8ed42a65dbd37355088df9dde122efc9653d.pdf

不幸的是,Benton和Hyland只允许相当“温和”的效果:像状态和环境monad这样的效果符合要求,但不包括异常,列表或IO这样的一般效果。 (这些有效计算的定点运算符在Haskell中称为mfix,类型签名为(a -> m a) -> m a,它们构成了递归操作符号的基础。)

如何扩展这项工作以涵盖任意单声道效果仍然是一个悬而未决的问题。尽管这些天似乎并没有引起太多关注。 (对于那些对lambda微积分,单子效应和基于图的计算之间的关系感兴趣的人,将成为一个很好的博士学位主题。)