How does Haskell tail recursion work?

后端 未结 6 1934
傲寒
傲寒 2020-12-07 15:30

I wrote this snippet of code and I assume len is tail-recursive, but a stack overflow still occurs. What is wrong?

myLength :: [a] -> Integer         


        
相关标签:
6条回答
  • 2020-12-07 16:08

    Seems like laziness causes len to build thunk:

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

    and so on. You must force len to reduce l every time:

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

    For more information, look http://haskell.org/haskellwiki/Stack_overflow.

    0 讨论(0)
  • 2020-12-07 16:08

    The foldl carries the same problem; it builds a thunk. You can use foldl' from Data.List to avoid that problem:

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

    The only difference between foldl and foldl' is the strict accumulation, so foldl' solves the problem in the same way as the seq and $! examples above. (const.succ) here works the same as (\a b -> a+1), though succ has a less restrictive type.

    0 讨论(0)
  • 2020-12-07 16:08

    Just so you know, there's a much easier way to write this function:

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

    Alex

    0 讨论(0)
  • 2020-12-07 16:14

    The simplest solution to your problem is turning on optimization.

    I have your source in a file called 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 The tests above were done on Mac OS X Snow Leopard 64bit with a GHC 7 and GHC 6.12.1, each in a 32 bit version. After you're downvote, I repeated this experiment on Ubuntu Linux with the following result:

    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$ 
    
    

    So, if you're interested we can continue to find out what is the reason, that this fails for you. I guess, GHC HQ, would accept it as a bug, if such a straight forward recursion over lists is not optimized into an efficient loop in this case.

    0 讨论(0)
  • 2020-12-07 16:17

    Remember that Haskell is lazy. Your computation (l+1) will not occur until it's absolutely necessary.

    The 'easy' fix is to use '$!' to force evaluation:

    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]
    
    0 讨论(0)
  • 2020-12-07 16:21

    eelco.lempsink.nl answers the question you asked. Here's a demonstration of Yann's answer:

    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
    

    foldl' goes through the list from left to right each time adding 1 to an accumulator which starts at 0.

    Here's an example of running the program:


    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>
    
    0 讨论(0)
提交回复
热议问题