F#で関数が末尾再帰であるかどうかを知る方法
-
03-07-2019 - |
質問
次の関数を作成しました:
let str2lst str =
let rec f s acc =
match s with
| "" -> acc
| _ -> f (s.Substring 1) (s.[0]::acc)
f str []
F#コンパイラがループに変換したかどうかを知るにはどうすればよいですか? Reflectorを使用せずに調べる方法はありますか(Reflectorの経験がなく、C#がわかりません)。
編集:また、内部関数を使用せずに末尾再帰関数を記述することは可能ですか、それともループが存在する必要がありますか?
また、F#std libには、指定された関数を何度も実行する関数があり、そのたびに最後の出力を入力として与えますか?文字列があるとしましょう。文字列に対して関数を実行してから、結果の文字列に対して再度実行したいなどです...
解決
残念ながら、簡単な方法はありません。
ソースコードを読んで型を使用し、検査によって何かがテールコールであるかどうかを判断するのはそれほど難しくありません(「最後のこと」であり、「try」ブロックではありません)。自分で推測し、間違いを犯します。簡単な自動化された方法はありません(生成されたコードの検査以外)。
もちろん、テストデータの大部分で関数を試して、それが爆発するかどうかを確認できます。
F#コンパイラは、すべてのテールコールに対して.tail IL命令を生成します(コンパイラフラグをオフにしない限り、デバッグ用にスタックフレームを保持する場合に使用されます)。ただし、直接テール再帰関数ループに最適化されます。 (編集:最近、この呼び出しサイトで再帰ループがないことを証明できる場合、F#コンパイラーも.tailの発行に失敗すると考えています。これは、.tailオペコードが多くのプラットフォームで少し遅いことを考えると最適化です)
'tailcall'は予約キーワードであり、F#の将来のバージョンでは、たとえば
tailcall func args
その後、テールコールでない場合は警告/エラーを受け取ります。
自然に末尾再帰ではない(したがって追加のアキュムレータパラメーターが必要な)関数のみが、「内部関数」イディオムに「強制」されます。
これはあなたが尋ねたもののコードサンプルです:
let rec nTimes n f x =
if n = 0 then
x
else
nTimes (n-1) f (f x)
let r = nTimes 3 (fun s -> s ^ " is a rose") "A rose"
printfn "%s" r
他のヒント
私は、Paul Grahamが On Lisp で定式化した経験則が好きです。再帰呼び出し出力を操作すると、呼び出しは末尾再帰ではありません。