2012-01-18 14 views
11

ハスケルの初心者として、私は関数(例えば、ロジスティックマップ)を何回も繰り返そうとしています。命令型言語ではこれは単純なループになりますが、Haskellではスタックオーバーフローが発生します。コードが動作の反復の数が少ないためHaskell:stackoverflowを使わずに多数の関数を繰り返す

main = print $ iter 1000000 

f x = 4.0*x*(1.0-x) 

iter :: Int -> Double 
iter 0 = 0.3 
iter n = f $ iter (n-1) 

を、しかし、百万回の反復のために私はスタック領域のオーバーフローを得る:例えば、このコードを取り、これが起こるんなぜ私が理解できない

Stack space overflow: current size 8388608 bytes. 
Use `+RTS -Ksize -RTS' to increase it. 

。尾の再帰はここでうまくいくはずです。 問題は遅延評価です。 $!またはseqをさまざまな位置に挿入することで、厳密な評価を強制するいくつかの方法を試しましたが、成功しませんでした。

関数を膨大な回数繰り返すハスケルの方法は何でしょうか?

私は、関連記事からの提案を試してみました:hereまたはhereが、私は常に多数の反復、例えば、main = print $ iterate f 0.3 !! 1000000のためにStackOverflowになってしまいました。

+3

問題は、 'iter(n-1)'を直接返さないので、末尾再帰を持たないということです。 – Simon

+0

人々はちょうど末尾再帰が何であるか分かりません。 FYI、この定義は間違っています:「関数の名前がその関数の最後の行に現れたとき」。 – Ingo

答えて

24

問題は、あなたの定義

iter :: Int -> Double 
iter 0 = 0.3 
iter n = f $ iter (n-1) 
が間違った方向に評価しようとする

ということです。いくつかの手順のためにそれを展開、我々はiter 0iter 1000000から

iter n = f (iter (n-1)) 
     = f (f (iter (n-2))) 
     = f (f (f (iter (n-3)))) 
     ... 

とコールスタック全体を取得何が評価される前に構築する必要があります。それは厳密な言葉で同じでしょう。あなたは評価の一部が反復する前に起こるようにそれを整理しなければなりません。場合には、コンパイラは、すでにそれらを追加しない - - それはスタックを消費することなくスムーズになります通常の方法は、次に

iter n = go n 0.3 
    where 
    go 0 x = x 
    go k x = go (k-1) (f x) 

正格性注釈を追加するように、蓄積パラメータを持つことです。

iterateのバリアントには、iterと同じ問題があります。コールスタックは、自分の場合と同じように、内部ではなく内部に組み込まれています。しかしiterate以来、コールスタックインサイドアウト、iterateの厳しいバージョン(またはそれ以前の反復が前に強制されている消費パターンが)問題を解決し、

iterate' :: (a -> a) -> a -> [a] 
iterate' f x = x `seq` (x : iterate' f (f x)) 

は問題なくiterate' f 0.3 !! 1000000を算出し、構築します。

+2

つまり、iterの元の定義はtail-recursiveではありません。 –

+7

これは折り畳みについて学ぶのに最適な時間です! 'iter n = foldl'(\ acc f - > f acc)0.3(複製nf) ' – rampion

+10

@JohnLしかし、怠惰/非拘束、テールのために、尾の再帰はハスケルにとって重要なものではありません再帰関数は大きなサンクを構築することによってスタックオーバーフローを引き起こす可能性があるので、厳密さ/熱心さも確実に確保する必要があります。一方、非テール再帰は、再帰呼び出しが適切な場所にある場合、スタックオーバーフローの問題はありません。遅延コンストラクタフィールド(ここでは、保護された再帰が重要な概念です)。 –

関連する問題