如何Haskell尾recursion工作?
我写了这段代码,我假设len
是尾recursion的,但仍然会发生堆栈溢出。 哪里不对?
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]
请记住,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]
似乎懒惰导致len
build立thunk:
len [1..100000] 0 -> len [2..100000] (0+1) -> len [3..100000] (0+1+1)
等等。 你必须每次强制len
减lessl
:
len (x:xs) l = l `seq` len xs (l+1)
有关更多信息,请参阅http://haskell.org/haskellwiki/Stack_overflow 。
褶皱带有同样的问题; 它build立了一个thunk。 您可以使用Data.List中的foldl来避免这个问题:
import Data.List myLength = foldl' (const.succ) 0
foldl和foldl'之间的唯一区别是严格的积累,所以foldl以和seq和$相同的方式解决问题! 上面的例子。 (const.succ)在这里和(\ ab – > a + 1)是一样的,虽然succ有一个限制较less的types。
解决您的问题最简单的方法就是开启优化。
我有一个叫做tail.h的文件
jmg $ ghc --make tail.hs -fforce-recomp [1 of 1]编译Main(tail.hs,tail.o) 连接尾巴... jmg $ ./tail 堆栈空间溢出:当前大小为8388608字节。 使用'+ RTS -Ksize -RTS'来增加它。 girard:haskell jmg $ ghc -O - 生成tail.hs -fforce-recomp [1 of 1]编译Main(tail.hs,tail.o) 连接尾巴... jmg $ ./tail 千万 JMG $
@Hynek -Pichi- Vychodil上面的testing是在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 其中len [] l = l len(x:xs)l = len xs(l + 1) main = print $ myLength [1..10000000] jmg @ girard:/ tmp $ ghc --version 格拉斯哥Glasgow Haskell编译系统,版本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]编译Main(length.hs,length.o) 链接长度... jmg @ girard:/ tmp $ time ./length 堆栈空间溢出:当前大小为8388608字节。 使用'+ RTS -Ksize -RTS'来增加它。 真正的0m1.359s 用户0m1.140s sys 0m0.210s jmg @ girard:/ tmp $ ghc -O --make length.hs -fforce-recomp [1 of 1]编译Main(length.hs,length.o) 链接长度... jmg @ girard:/ tmp $ time ./length 千万 实际0m0.268s 用户0m0.260s sys 0m0.000s JMG @吉拉德:/ tmp目录$
所以,如果你有兴趣,我们可以继续找出是什么原因,这对你来说是失败的。 我想GHC HQ会接受它作为一个bug,如果这样一个简单的列表recursion在这种情况下没有被优化成一个有效的循环。
就这样你知道,这个函数的编写有一个更简单的方法:
myLength xs = foldl step 0 xs where step acc x = acc + 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
foldl'从左到右通过列表,每次将1加到从0开始的累加器。
这是一个运行程序的例子:
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>