Haskell尾递归如何工作?

时间:2009-01-05 12:04:37

标签: haskell tail-recursion

我写了这段代码,我假设len是尾递归的,但仍然会发生堆栈溢出。有什么问题?

myLength :: [a] -> Integer

myLength xs = len xs 0
    where len [] l = l
          len (x:xs) l = len xs (l+1)

main = print $ myLength [1..10000000]

6 个答案:

答案 0 :(得分:40)

请记住,Haskell很懒惰。在绝对必要之前,您的计算(l + 1)不会发生。

'简单'修复是使用'$!'强迫评估:

myLength :: [a] -> Integer
myLength xs = len xs 0
where len [] l = l
      len (x:xs) l = len xs $! (l+1)

      main = print $ myLength [1..10000000]

答案 1 :(得分:14)

似乎懒惰导致len构建thunk:

len [1..100000] 0
-> len [2..100000] (0+1)
-> len [3..100000] (0+1+1)

等等。您必须每次强制len减少l

len (x:xs) l = l `seq` len xs (l+1)

有关详细信息,请查看http://haskell.org/haskellwiki/Stack_overflow

答案 2 :(得分:4)

foldl带有同样的问题;它构建了一个thunk。您可以使用Data.List中的foldl'来避免该问题:

import Data.List
myLength = foldl' (const.succ) 0

foldl和foldl之间的唯一区别是严格的积累,所以foldl'以与seq和$相同的方式解决问题!上面的例子。 (const.succ)这里的作用与(\ a b - > a + 1)相同,尽管succ的限制性较小。

答案 3 :(得分:2)

解决问题的最简单方法是启用优化。

我的源代码名为tail.hs。

jmg$ ghc --make tail.hs -fforce-recomp
[1 of 1] Compiling Main             ( tail.hs, tail.o )
Linking tail ...
jmg$ ./tail 
Stack space overflow: current size 8388608 bytes.
Use `+RTS -Ksize -RTS' to increase it.
girard:haskell jmg$ ghc -O --make tail.hs -fforce-recomp
[1 of 1] Compiling Main             ( tail.hs, tail.o )
Linking tail ...
jmg$ ./tail 
10000000
jmg$ 

@Hynek -Pichi- Vychodil 上述测试在Mac OS X Snow Leopard 64bit上进行,GHC 7和GHC 6.12.1,每个都是32位版本。在你投票之后,我在Ubuntu Linux上重复了这个实验,结果如下:

jmg@girard:/tmp$ cat length.hs
myLength :: [a] -> Integer

myLength xs = len xs 0
    where len [] l = l
          len (x:xs) l = len xs (l+1)

main = print $ myLength [1..10000000]

jmg@girard:/tmp$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 6.12.1
jmg@girard:/tmp$ uname -a
Linux girard 2.6.35-24-generic #42-Ubuntu SMP Thu Dec 2 02:41:37 UTC 2010 x86_64 GNU/Linux
jmg@girard:/tmp$ ghc --make length.hs -fforce-recomp
[1 of 1] Compiling Main             ( length.hs, length.o )
Linking length ...
jmg@girard:/tmp$ time ./length 
Stack space overflow: current size 8388608 bytes.
Use `+RTS -Ksize -RTS' to increase it.

real    0m1.359s
user    0m1.140s
sys 0m0.210s
jmg@girard:/tmp$ ghc -O --make length.hs -fforce-recomp
[1 of 1] Compiling Main             ( length.hs, length.o )
Linking length ...
jmg@girard:/tmp$ time ./length 
10000000

real    0m0.268s
user    0m0.260s
sys 0m0.000s
jmg@girard:/tmp$ 

所以,如果你有兴趣,我们可以继续找出原因,这对你来说是失败的。我想,GHC HQ会接受它作为一个错误,如果在这种情况下对列表的这种直接递归没有优化为有效的循环。

答案 4 :(得分:1)

您知道,编写此函数的方法更简单:

myLength xs = foldl step 0 xs where step acc x = acc + 1

亚历

答案 5 :(得分:1)

eelco.lempsink.nl回答了你问的问题。以下是Yann答案的演示:

module Main
    where

import Data.List
import System.Environment (getArgs)

main = do
  n <- getArgs >>= readIO.head
  putStrLn $ "Length of an array from 1 to " ++ show n
               ++ ": " ++ show (myLength [1..n])

myLength :: [a] -> Int
myLength = foldl' (const . succ) 0
每次将1添加到从0开始的累加器时,

foldl'从左到右遍历列表。

以下是运行该程序的示例:


C:\haskell>ghc --make Test.hs -O2 -fforce-recomp
[1 of 1] Compiling Main             ( Test.hs, Test.o )
Linking Test.exe ...

C:\haskell>Test.exe 10000000 +RTS -sstderr
Test.exe 10000000 +RTS -sstderr

Length of an array from 1 to 10000000: 10000000
     401,572,536 bytes allocated in the heap
          18,048 bytes copied during GC
           2,352 bytes maximum residency (1 sample(s))
          13,764 bytes maximum slop
               1 MB total memory in use (0 MB lost due to fragmentation)

  Generation 0:   765 collections,     0 parallel,  0.00s,  0.00s elapsed
  Generation 1:     1 collections,     0 parallel,  0.00s,  0.00s elapsed

  INIT  time    0.00s  (  0.00s elapsed)
  MUT   time    0.27s  (  0.28s elapsed)
  GC    time    0.00s  (  0.00s elapsed)
  EXIT  time    0.00s  (  0.00s elapsed)
  Total time    0.27s  (  0.28s elapsed)

  %GC time       0.0%  (0.7% elapsed)

  Alloc rate    1,514,219,539 bytes per MUT second

  Productivity 100.0% of total user, 93.7% of total elapsed


C:\haskell>