テールコールの最適化が行われるように、単純な再帰の例を調整します(StackOverflowError
ではなく)。再帰とStackOverflowError
count 0 = 0
count n = succ (count (pred n))
count 100000
テールコールの最適化が行われるように、単純な再帰の例を調整します(StackOverflowError
ではなく)。再帰とStackOverflowError
count 0 = 0
count n = succ (count (pred n))
count 100000
これは「長さ/折りたたみ」の種類のスタックオーバーフローの種類です。これは、結果を構成する関数アプリケーションの厳密な引数を計算するために再帰関数が適用されたときに発生します。比較:f
が第2引数で厳しいとき
-- naive computation of list length
-- this is not like it's defined in Frege, of course
length [] = 0
length (_:xs) = 1 + length xs
これをまたfoldr f
で発生します。
は(SO)スタックオーバーフローのための他の可能な理由があります。テール再帰関数がwhileループにコンパイルされるため、これはFregeで最も効率的に処理されます。他のJVM言語とは異なり、実際にSOを引き起こすことはありません。
a
テールコールb
、テールはc
をコールし、テールはa
を呼び出します)。これは決してFregeのSOをもたらさないはずです。foldl
で起こります。 Fregeでは、標準fold
はアキュムレータに厳格であり、多くの場合免除されています。しかし、長いリストでは次のようにオーバーフローします。fold (<>) mempty (map (Just . Sum) [1..10000])
even 0 = true
even n = case even (pred n) of
true -> false
false -> true
第2の式がeven n = not (even (pred n))
と意味的に等価であり、したがって4の一層悪質な変異体である:ここ
は最後の場合の例です。
あなたの質問に答えるには、アキュムレータを使用してテール再帰バージョンを作成することができます。
count n = go 0 n
where
go acc 0 = acc
go acc n = go (succ acc) (pred n)
Haskellであなたの例も失敗したことは注目におそらく価値がある:
GHCi, version 7.6.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> let count 0 = 0; count n = succ (count (pred n))
Prelude> count 10000
10000
Prelude> count 100000
100000
Prelude> count 1000000
1000000
Prelude> count 10000000
10000000
Prelude> count 100000000
*** Exception: stack overflow
それだけではるかに高い数字で溢れている理由は、HaskellのRTSがより適している方法で、RAMを管理していることですJVMは起動時に小さなデフォルトスタックを割り当てますが、最高で数千のコール深度に対応しています。
あなたは大きなスタックを割り当てるためにJVMを強制するときにもフレーゲでプログラムをはるかに大きい数字を計算することができます
java -Xss512m ....
経験は、サイズ512メートルのスタックがapproximatly千万のための呼び出しの深さを可能にすることを示しています単一の引数関数。
ただし、これは一般的な解決策ではありません。これは、JVMがこのスタックサイズのすべてすべてのスレッドを作成するためです。したがって、貴重なRAMが無駄になります。そしてたくさんのRAMがあっても、たぶん2g以上のスタックを作ることはできません。
Fregeの次のメジャーバージョンでは、スタックオーバーフローの種類3と4(上記参照)の病理学的なケースはうまくいけばうまく管理されます。しかし、今日の時点では、そのような構成は、JVMスタックに収まる小さな問題のサイズに対してのみ機能します。
おかげで、あなたの詳細な回答のために非常に多くの!私はHaskellとFregeには新しく、とても面白そうです。私はある程度関数型プログラミングに精通していますが、かなりの学習曲線が先にあります。ハスケルの例もテストしたことに感謝します。私が使っていたマシンでそれをやってみたところ、ubuntuで500MBのダウンロードをしていたので、後で残しました。私は、このお返事に戻って来ると思います。 –