懒惰的评价和嵌套的thunk吃掉了记忆

时间:2014-08-13 06:27:15

标签: haskell lazy-evaluation lambda-calculus

我正在研究一个小型的lambda演算引擎,我希望它像Haskell一样懒惰。我试图,至少就目前而言,坚持Haskell的规则,以便我不必重新思考一切,但我不想盲目地这样做。

我理解Haskell在需要它之前不会评估一个术语。这是我的第一个怀疑。我理解价值是"需要" when是内置函数的参数(因此在(func x)中,如果x是内置函数并且需要func,则需要(func x))或因为是函数被调用(因此在(x y)中,如果需要x,则需要(x y)

我的第二个问题是,说我有这个递归功能:

let st = \x -> (st x)

到目前为止我实现它的方式是,如果我将其称为(st "hi"),则"hi"不会被评估,但会被包含在包含该术语的thunk中范围,将被添加为" x"到st的范围。然后,再次评估st时,将在x中的(st x)周围创建另一个thunk,其中包含术语x及其范围,其中包含x的另一个定义{1}},即" hi"。这样,嵌套的thunk将继续构建,直到我的内存不足为止。

我在GHCI上面测试了我的代码,内存还可以。然后我测试了这个:

let st = \x -> (st (id x))

并在应用程序崩溃之前建立内存。显然,当参数是函数调用时,GHCI(或Haskell?)只使用thunk;在其他每种情况下,它都使用术语的值。这是我可以轻松实现的。

我想到的另一个选项是,在评估函数调用和为参数创建thunk之前,不允许嵌套thunk,评估整个当前作用域以确保没有新的thunk将包含另一个thunk。我认为这仍然应该让我创建无限列表并获得延迟评估的一些(或全部?)好处,甚至可以防止应用程序在调用let st = \x.(st (id x))函数时崩溃。

我确定有很多方法可以实现懒惰评估,但很难弄清楚每种方式的优缺点。是否有一些列表包含最常见的惰性评估实现及其优缺点?而且,Haskell是如何做到的?

1 个答案:

答案 0 :(得分:1)

这远不是一个完整的答案,但也许它足以取得进展。

首先,功能

let st = \x -> (st x)

st绑定到lambda。这可能是指向lambda的thunk,或者它可能不是(Haskell Report只指定非严格的评估。如果编译器可以证明早期评估thunk并不会改变程序的语义,那么&# 39;可以自由地这样做;证明源代码中的lambda可以在不改变语义的情况下被评估为WHNF,这是微不足道的。)

无论如何,假设您强制评估st "hi"。应用lambda(beta减少)后,下一步是st "hi"。因此,这种beta减少无休止地循环,但它永远不会创建新数据。也就是说,没有必要在thunk中包装任何东西。因此,虽然它永远循环,但是这个应用程序并没有分配内存。

将此与

进行比较
let st = \x -> (st (id x))

在这里,如果我们beta减少:

st "hi"
st (id "hi")
st (id (id "hi"))
st (id (id (id "hi")))

等。在这里,因为永远不会评估st的参数,它会构建一个包含新id应用程序的无休止的thunk链,从而消耗更多的内存。

我认为您在实施过程中遇到的问题是您正在包装" hi"在lambda下面。相反,任何产生" hi"应该创建thunk然后传递,直到它被评估。那么"嗨"只被包裹一次而不是每一步。

编辑:忘记回答您的第一个问题,但我不能做出比@ leftaroundabout更好的建议来阅读弱头普通表格。关于SO还有其他问题,例如: Haskell: What is Weak Head Normal Form?Weak head normal form and order of evaluation