我在学习功能响应式编程时发现了这个声明,来自Hai Liu 和 Paul Hudak的“用箭头堵住空间泄漏” (第 5 页):
Suppose we wish to define a function that repeats its argument indefinitely: repeat x = x : repeat x or, in lambdas: repeat = λx → x : repeat x This requires O(n) space. But we can achieve O(1) space by writing instead: repeat = λx → let xs = x : xs in xs
这里的差异似乎很小,但它极大地提高了空间效率。为什么以及如何发生?我做出的最好的猜测是手动评估它们:
r = \x -> x: r x
r 3
-> 3: r 3
-> 3: 3: 3: ........
-> [3,3,3,......]
如上所述,我们需要为这些递归创建无限的新 thunk。然后我尝试评估第二个:
r = \x -> let xs = x:xs in xs
r 3
-> let xs = 3:xs in xs
-> xs, according to the definition above:
-> 3:xs, where xs = 3:xs
-> 3:xs:xs, where xs = 3:xs
在第二种形式中,xs
出现并且可以在它出现的每个地方之间共享,所以我想这就是为什么我们只能需要O(1)
空格而不是O(n)
. 但我不确定我是否正确。
顺便说一句:关键字“共享”来自同一篇论文的第 4 页:
这里的问题是标准的按需调用评估规则无法识别该函数:
f = λdt → integralC (1 + dt) (f dt)
是相同的:
f = λdt → let x = integralC (1 + dt) x in x
前一种定义导致在对 f 的递归调用中重复工作,而在后一种情况下,计算是共享的。