Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/jsf-2/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 惰性计算和嵌套thunk消耗内存_Haskell_Lazy Evaluation_Lambda Calculus - Fatal编程技术网

Haskell 惰性计算和嵌套thunk消耗内存

Haskell 惰性计算和嵌套thunk消耗内存,haskell,lazy-evaluation,lambda-calculus,Haskell,Lazy Evaluation,Lambda Calculus,我正在研究一个小型的lambda微积分引擎,我希望它像Haskell一样懒惰。我正在努力,至少现在,坚持Haskell的规则,这样我就不必重新思考一切,但我不想盲目地这样做 我知道Haskell不会评估一个术语,直到需要它的值。这是我的第一个疑问。我理解当值是内置函数的参数时是“需要的”(因此在(func x),如果func是内置函数并且(func x)是需要的),或者因为是要调用的函数(因此在(xy),如果(xy)是需要的,则需要x) 我的第二个问题是,假设我有这个递归函数: let st =

我正在研究一个小型的lambda微积分引擎,我希望它像Haskell一样懒惰。我正在努力,至少现在,坚持Haskell的规则,这样我就不必重新思考一切,但我不想盲目地这样做

我知道Haskell不会评估一个术语,直到需要它的值。这是我的第一个疑问。我理解当值是内置函数的参数时是“需要的”(因此在
(func x)
,如果
func
是内置函数并且
(func x)
是需要的),或者因为是要调用的函数(因此在
(xy)
,如果
(xy)
是需要的,则需要
x

我的第二个问题是,假设我有这个递归函数:

let st = \x -> (st x)
到目前为止,我实现它的方式是,如果我像
(st“hi”)
那样调用它,
“hi”
将不会被计算,而是被包装在一个thunk中,它包含术语及其范围,它将作为“x”添加到
st
的主体范围中。然后,当再次评估
st
时,将围绕
(st x)
中的
x
创建另一个thunk,该thunk将包含术语
x
及其范围,该范围包含
x
的另一个定义,即“hi”。这样,嵌套的thunk将不断累积,直到内存耗尽

我在GHCI中测试了上面的代码,内存正常。然后我测试了这个:

let st = \x -> (st (id x))
在应用程序崩溃之前,内存一直在积累。显然,当参数是函数调用时,GHCI(或Haskell?)只使用thunks;在其他情况下,它使用术语的值。这是我可以轻松实现的

我想到的另一个选择是,在计算函数调用和为参数创建thunk之前,不允许嵌套thunk by,计算整个当前范围以确保新thunk不会包含另一个thunk。我认为这仍然可以让我创建无限列表,并获得惰性计算的一些(或全部?)好处,甚至可以防止在调用
let st=\x.(st(id x))
函数时应用程序崩溃


我相信有很多方法可以实现惰性评估,但很难找出每种方法的优缺点。是否有一些列表包含最常见的惰性评估实现及其优缺点?还有,哈斯克尔是如何做到的?

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

第一,功能

let st = \x -> (st x)
st
绑定到lambda。这可能是指向lambda的thunk,也可能不是(Haskell报告只指定非严格的求值。如果编译器可以证明早期求值thunk不会改变程序的语义,那么可以自由地这样做;证明源代码中的lambda可以在不改变语义的情况下求值为WHNF是很简单的)

无论如何,假设您强制计算
st“hi”
。应用lambda(β降低)后,下一步是
st“hi”
。因此,这种beta缩减会无休止地循环,但它永远不会创建新数据。也就是说,没有必要用砰砰声来包装任何东西。因此,尽管它永远循环,但这个应用程序不分配内存

将此与

let st = \x -> (st (id x))
在这里,如果我们减少:

st "hi"
st (id "hi")
st (id (id "hi"))
st (id (id (id "hi")))
等等。在这里,因为
st
的参数从未被计算过,所以它建立了一个无休止的thunk链来包装一个新的
id
应用程序,消耗了越来越多的内存

我认为实现中存在的问题是在lambda下面包装“hi”。相反,任何产生“hi”的东西都应该创建thunk,然后传递它,直到对其进行评估。然后,“hi”只被包装一次,而不是在每一步


编辑:忘了回答你的第一个问题,但我没有比@leftaroundabout建议阅读弱头范式更好的了。关于SO还有其他问题,例如和

阅读弱头范式等。例如,我这样做的方式甚至不是检查参数,只是盲目地将它们包装在一个包含范围的thunk中,所以是的,包装“hi”,然后是包含“hi”的
x
,然后是另一个
x
,包含前面的
x
,等等。但你的意思是,我应该检查这个术语是否是变量,如果是的话,使用它所代表的thunk,而不是将它包装成另一个thunk,对吗?这就是GHCi所做的吗(至少基本上如此)?@JuanLuisSoldi在ghc中,thunks是一种价值属性;(几乎)任何值都可以评估或不评估(thunk)。在第二个示例中,ghc将应用
IDX
(正是这个函数应用程序创建了新值/thunk)并将该值传递给递归调用。听起来你的编译器做了些别的事情,我想我对它的理解还不足以提出任何建议。但是你根据期限结构来决定是否创建一个新thunk的想法是可行的。它是否适用于
idx
?难道它不应该把它放在一个重击没有应用它,因为它是懒惰的?这不是我运行GHCi时崩溃的原因吗?@JuanLuisSoldi抱歉,我不清楚。当我说应用了这个函数时,我的意思是ghc做了类似于
let x'=id x in st x'
,其中
x'
是传递到下一阶段的thunk。(函数应用与将结果值减少为WHNF不同)。我不确定ghc是如何决定什么不需要包装的,这部分发生在我不太熟悉的管道的STG阶段。全部细节可能在