Как работает хвостовая рекурсия Haskell?
Я написал этот фрагмент кода, и я предполагаю, что 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]
Ответы
Ответ 1
Помните, что 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]
Ответ 2
Кажется, что лень вызывает 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.
Ответ 3
Склад имеет ту же проблему; он строит кусок. Вы можете использовать foldl 'из Data.List, чтобы избежать этой проблемы:
import Data.List
myLength = foldl' (const.succ) 0
Единственное различие между foldl и foldl '- это строгое накопление, поэтому foldl' решает проблему так же, как seq и $! примеры выше.
(const.succ) здесь работает так же, как (\ a b → a + 1), хотя succ имеет менее ограничительный тип.
Ответ 4
Простейшим решением вашей проблемы является оптимизация.
У меня есть источник в файле 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$
@Хинек-Пичи-Выходил
Тесты были выполнены на Mac OS X Snow Leopard 64bit с GHC 7 и GHC 6.12.1, каждая в 32-битной версии. После того, как вы сделали downvote, я повторил этот эксперимент на Ubuntu Linux со следующим результатом:
[email protected]:/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]
[email protected]:/tmp$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 6.12.1
[email protected]:/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
[email protected]:/tmp$ ghc --make length.hs -fforce-recomp
[1 of 1] Compiling Main ( length.hs, length.o )
Linking length ...
[email protected]:/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
[email protected]:/tmp$ ghc -O --make length.hs -fforce-recomp
[1 of 1] Compiling Main ( length.hs, length.o )
Linking length ...
[email protected]:/tmp$ time ./length
10000000
real 0m0.268s
user 0m0.260s
sys 0m0.000s
[email protected]:/tmp$
Итак, если вам интересно, мы можем продолжать выяснять, в чем причина, что это не подходит для вас. Я думаю, GHC HQ примет это как ошибку, если такая прямая рекурсия над списками в этом случае не оптимизирована в эффективный цикл.
Ответ 5
Просто, чтобы вы знали, есть гораздо более простой способ написать эту функцию:
myLength xs = foldl step 0 xs where step acc x = acc + 1
Алекс
Ответ 6
eelco.lempsink.nl отвечает на заданный вами вопрос. Вот демонстрация ответа Янна:
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>