関数型言語でのプログラムは、スタックオーバーフローを持っている可能性がありますか?
質問
私はocamlを学ぶために始めていますし、本当に言語における再帰の力を鑑賞しています。しかし、私は心配しています一つのことは、オーバーフローをスタックされます。
OCamlは関数呼び出しのスタックを使用している場合、それは最終的にスタックオーバーフローではないでしょうか?例えば、私は以下の機能を持っている場合:
let rec sum x =
if x > 1 then f(x - 1) + x
else x;;
これは、最終的には、スタックオーバーフローを引き起こす必要があります。私は(再帰を使用して)C ++で同等のことを行うことだった場合、私はそれがオーバーフローすることを知っています。
だから私の質問は、スタックをオーバーフローから関数型言語を防ぐためにセーフガードに建てられ、そこにされているのですか?そうでない場合、彼らはこのようなあまり有用ではない、アルゴリズムを合計上記以来、forループでの手続きのスタイルで書かれた、任意の数(DIS-に関する整数オーバーフロー)を扱うことができる?
解決
関数型言語;-)の全ての(まともな実装は末尾再帰を最適化しますが、再帰呼び出しは、最後の操作(それはさらに続いする必要があります)ではないので、それは、あなたがここで何をやっているではありません。
ですから、人はすぐに、つまり、私は「する可能O'Caml構文のネット末尾再帰IS(引数として蓄積されている現在の合計をとる)ので、オプティマイザはその仕事ができる補助機能を使用することを学びますm個の錆びます:
let sum x =
aux(x)(0);;
let rec aux x accum =
if x > 1 then aux(x - 1)(accum + x)
else (accum + x);;
ここで、合計は再帰自体前に、再帰呼び出し、すなわちへの引数として起こり、および(再帰が発生する必要がある最後の事ですので!)ので、テール最適化がで蹴ることができます。
他のヒント
関数型言語は、一般的にはるかに大きなスタックを持っています。例えば、私はOCamlでのスタック制限をテストするために、具体的関数を書いて、それはそれはbarfed前に10,000のコールを過剰になりました。しかし、あなたのポイントは有効です。スタックオーバーフローはまだあなたが関数型言語にに注意する必要があるものです。
関数型言語が再帰への依存度を軽減するために使用する戦略の一つは、末尾呼び出しの最適化を使用することですに。現在の関数の次の再帰呼び出しには、関数の最後のステートメントである場合は、現在のコールスタックとその場所にインスタンス化新しいコールから破棄することができます。生成されたアセンブリ命令は、命令的スタイルでしばらくループのためのものと基本的に同じになります。
再帰が最後のステップではありませんので、あなたの関数は末尾呼び出し最適化可能ではありません。これは、最初に返す必要がありますし、それが結果にXを追加することができます。通常、これは、周りを取得するのは簡単です、あなただけの他のパラメータと一緒にアキュムレータを渡すヘルパー関数を作成します。
let rec sum x =
let sum_aux accum x =
if x > 1 then sum_aux (accum + x) (x - 1)
else x
in sum_aux 0 x;;
このようなスキームのようないくつかの関数型言語は、末尾再帰するの 強いなければならないことを指定します>は、反復に相当するように最適化されます。したがって、Schemeで末尾再帰関数は、それが(それはまた、再帰またはエンド以外の他の場所で相互再帰に従事していないことを、もちろん、前提)再帰回数に関係なく、スタックオーバーフローが発生することはありません。
他のほとんどの関数型言語を効率的に実装することが末尾再帰を必要としません。いくつかは、他にはない、そうすることを選択したが、それは実現するのは比較的簡単ですので、私はほとんどの実装がそうすることを期待します。
それは確かに簡単です。目的Camlのは、ののライブラリList
関数はスタック・安全をされていない長いリストのためのものでは珍しいです。 ユニゾンのようなアプリケーションでは、実際には、スタックセーフでCamlの標準List
ライブラリを交換しました版。他のほとんどの実装では、スタックとのより良い仕事をします。 (免責事項:私の情報は目的Camlの3.08を説明し、現在のバージョン、3.11は、良いかもしれません)。
はあなたの深いので、それはスタックを使用していないという点で異例です再帰を使用すると、ヒープを使い果たすまで続けます。これは継続のにしてコンパイルアンドリューアペルの優れた著書で説明しています。
私はここで深刻な問題はないと思います。それはのように、あなたが関数型言語で行うことが可能性が高くなります再帰的なコードの多くを書くことにしようとしているならば、あなたは非尾の呼び出しを意識する必要があることをより多くの「気づきのポイント」だとスタックサイズのデータの大きさに比べて、あなたが処理されます。
これはトリッキーです。最も基本的には、ほとんどの関数型言語ランタイムは、通常の反復プログラムが使用するよりもはるかに大きなスタックを要求するということです。しかし、それに加えて、関数型言語のコンパイラが原因言語の多くの厳しい制約のためにはるかに非再帰的に再帰的なコードを変換することができます。