Haskell 为什么递归'let'使空间更高效?
我在研究函数式反应式编程时发现了这句话,作者是刘海和保罗·胡达克(第5页): 这里的差异似乎很小,但它极大地促进了空间效率。为什么以及如何发生?我做的最好的猜测是手工评估:Haskell 为什么递归'let'使空间更高效?,haskell,frp,Haskell,Frp,我在研究函数式反应式编程时发现了这句话,作者是刘海和保罗·胡达克(第5页): 这里的差异似乎很小,但它极大地促进了空间效率。为什么以及如何发生?我做的最好的猜测是手工评估: r = \x -> x: r x r 3 -> 3: r 3 -> 3: 3: 3: ........ -> [3,3,3,......] 如上所述,我们需要为这些递归创建无限的新thunk。然后我尝试评估第二个: r = \x -> let
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,而在后一种情况下,计算是共享的
简单地说,变量是共享的,但函数应用程序不是。在
repeat x = x : repeat x
从语言的角度来看,(co)递归调用repeat与同一个参数是巧合的。因此,如果没有额外的优化(称为静态参数转换),函数将被反复调用
但是当你写作的时候
repeat x = let xs = x : xs in xs
repeat x = let xs = x : xs in xs
没有递归函数调用。取一个x
,并使用它构造一个循环值xs
。所有共享都是明确的
如果您想更正式地理解它,您需要熟悉惰性评估的语义,例如。您关于共享
xs
的直觉是正确的。在写作时,以重复而非整体的方式重述作者的例子:
repeat x = x : repeat x
该语言无法识别右侧的repeat x
与表达式x:repeat x
生成的值相同。如果你写
repeat x = let xs = x : xs in xs
repeat x = let xs = x : xs in xs
您正在显式创建一个结构,该结构在计算时如下所示:
{hd: x, tl:|}
^ |
\________/
通过图片最容易理解:
- 第一版
创建一个以thunk结尾的repeat x = x : repeat x
构造函数链,当您需要更多构造函数时,它将用更多构造函数替换自己。因此,O(n)空间(:)
- 第二版
使用repeat x = let xs = x : xs in xs
来“打结”,创建一个引用自身的let
构造函数(:)