質問

次の関数を作成しました:

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 で定式化した経験則が好きです。再帰呼び出し出力を操作すると、呼び出しは末尾再帰ではありません。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top