Xbox での開発用に、F# 仕様からの 最終的なワークフローの修正バージョンを使用しています。Xbox の .net フレームワークはテール コールをサポートしていないようです。このため、コンパイル時に末尾呼び出しの最適化を無効にする必要があります。
最初は、この制限が計算式でのあらゆる形式のループの使用を防止するように思われるかもしれませんが、私は当初、「ステッピング」によってその問題を回避できると考えていました。 f を呼び出すラムダを含む 最終的に値を返します。
実験の結果、while ループ (計算式で使用してもスタック オーバーフローは発生しません) については正しかったが、再帰関数についてはそうではありませんでした。
明確にするために、これは機能します:
// Wait until "start" or "A" is pressed on one of the gamepads.
// Set "player" when that happens.
let player : PlayerIndex option ref = ref None
while (!player).IsNone do
for p in all_players do
let state = GamePad.GetState(p)
if state.IsConnected
&& (state.Buttons.Start = ButtonState.Pressed
|| state.Buttons.A = ButtonState.Pressed) then
player := Some p
do! sys.WaitNextFrame()
これにより、スタック オーバーフローが発生します。
// Wait until "start" is pressed on the controlling gamepad.
let rec wait() = task {
input.Update()
if not (input.IsStartPressed()) then
do! sys.WaitNextFrame()
do! wait()
}
2 番目のケースでは、スタック トレースは "bind@17-1" への長い一連の呼び出しを示しています。そのコードを以下に示します。スタック トレースに表示される行番号は 17 行目です。
let rec bind k e =
match e with
| Completed r ->
Running(fun () -> k r)
| Running f ->
Running(fun () -> f() |> bind k) // line 17
| Blocked(dt, f) ->
Blocked(dt, fun () -> f() |> bind k)
| BlockedNextFrame f ->
BlockedNextFrame(fun () -> f() |> bind k)
| Yield f ->
Yield(fun () -> f() |> bind k)
どこが間違っていますか?「ステップ可能な再帰」がスタックオーバーフローに対して無害であるという私の推論は間違っていますか? バインドの実装に何か問題がありますか?
ああ、私の頭!再帰を伴う継続渡しは頭痛の種です...
編集:「スタックオーバーフローに関して無害なステップ可能な再帰」という名前が付けられました。いわゆるトランポリンです。