我正在研究一个小型的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是如何做到的?
答案 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