16

私は次の関数を書きました:

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 には、特定の関数を何度も実行する関数があり、そのたびに最後の出力が入力として与えられますか? 文字列があるとしましょう。文字列に対して関数を実行し、結果の文字列に対して再度実行します...

4

2 に答える 2

22

残念ながら、簡単な方法はありません。

ソース コードを読んで型を使用し、何かがテール コールであるかどうかを検査によって判断するのはそれほど難しくありません (それは「最後」であり、「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
于 2009-04-30T13:23:42.640 に答える
3

私は、Paul Graham がOn Lispで定式化した経験則が好きです。たとえば、再帰呼び出しの出力を操作するなど、やるべきことが残っている場合、その呼び出しは末尾再帰ではありません。

于 2012-04-17T11:48:44.987 に答える