代码之家  ›  专栏  ›  技术社区  ›  Hynek -Pichi- Vychodil Paulo Suassuna

Haskell尾部递归是如何工作的?

  •  48
  • Hynek -Pichi- Vychodil Paulo Suassuna  · 技术社区  · 15 年前

    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 回复  |  直到 15 年前
        1
  •  40
  •   eelco    15 年前

    记住哈斯克尔是懒惰的。在绝对必要之前,您的计算(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]
    
        2
  •  14
  •   mattiast    15 年前

    似乎懒惰会导致 len 要构建thunk:

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

    等等你必须强迫 伦恩 减少 l 每一次:

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

    有关更多信息,请参阅 http://haskell.org/haskellwiki/Stack_overflow .

        3
  •  4
  •   Yann Vernier Yann Vernier    15 年前

    foldl也有同样的问题;它会发出砰砰的一声。您可以使用foldl“from Data.List”来避免该问题:

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

    (const.succ)在这里的工作原理与(\a b->a+1)相同,但succ的限制性较小。

        4
  •  2
  •   jmg    13 年前

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

    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$ 
    

    @海内克-皮奇-维乔迪

    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会将其视为一个bug。

        5
  •  1
  •   Alex Fort    15 年前

    正如您所知,有一种更简单的方法来编写此函数:

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

    亚历克斯

        6
  •  1
  •   Michael Steele    15 年前

    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
    

    每次向从0开始的累加器添加1时,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>