Bil*_*ill 7 haskell lazy-evaluation
规范的实现length :: [a] -> Int是:
length [] = 0
length (x:xs) = 1 + length xs
Run Code Online (Sandbox Code Playgroud)
这是非常漂亮的,但由于它使用线性空间而遭受堆栈溢出.
尾递归版本:
length xs = length' xs 0
where length' [] n = n
length' (x:xs) n = length xs (n + 1)
Run Code Online (Sandbox Code Playgroud)
不会遇到这个问题,但我不明白这是如何在懒惰的语言中以恒定的空间运行的.
运行时(n + 1)列表中的运行时是否累积了大量的thunks?这个函数Haskell不应该消耗O(n)空间并导致堆栈溢出吗?
(如果重要的话,我正在使用GHC)
Nor*_*sey 15
是的,你遇到了累积参数的常见陷阱.通常的办法是对累积参数进行严格评估; 为此,我喜欢严格的应用程序运算符$!.如果你不强制严格,GHC的优化器可能会认为这个函数是严格的,但它可能不是.肯定不是依赖它 - 有时候你想要一个累积的参数被懒惰地评估,O(N)空间就好了,谢谢.
如何在Haskell中编写一个恒定空间长度函数?
如上所述,使用严格的应用程序运算符强制评估累积参数:
clength xs = length' xs 0
where length' [] n = n
length' (x:xs) n = length' xs $! (n + 1)
Run Code Online (Sandbox Code Playgroud)
$!is 的类型(a -> b) -> a -> b,它强制a在应用函数之前进行评估.
C. *_*ann 12
在GHCi中运行第二个版本:
> length [1..1000000]
*** Exception: stack overflow
Run Code Online (Sandbox Code Playgroud)
所以回答你的问题:是的,它确实会遇到这个问题,就像你期望的那样.
但是,GHC比普通编译器更聪明; 如果您使用优化结果进行编译,它将为您修复代码并使其在恒定空间中工作.
更一般地说,有一些方法可以在Haskell代码中的特定点强制严格,防止构建深层嵌套的thunk.的通常的例子是foldl与foldl':
len1 = foldl (\x _ -> x + 1) 0
len2 = foldl' (\x _ -> x + 1) 0
Run Code Online (Sandbox Code Playgroud)
这两个函数都是左边的折叠,它们执行"相同"的操作,除了foldl在foldl'严格的情况下是懒惰的.结果是len1在GHCi中具有堆栈溢出的模具,同时len2正常工作.