Jon が既に言ったように、関数は末尾再帰ではありません。基本的な問題は、自分自身を再帰的に複数回呼び出す必要があることです(リスト内のすべての要素に対して 1回xs
、これは に渡されるラムダ関数で行われますList.map
)。
実際に複数の再帰呼び出しを行う必要がある場合は、継続渡しスタイルまたは命令スタックを使用することがおそらく唯一のオプションです。継続の背後にある考え方は、すべての関数が (最後の引数として) 別の関数を取り、結果が利用可能になったときに実行する必要があるというものです。
次の例は、通常バージョン (左側) と継続ベース (右側) を示しています。
let res = foo a b fooCont a b (fun res ->
printfn "Result: %d" res printfn "Result: %d" res)
fold
関数を継続渡しスタイルで記述するには、継続ベースの関数も使用する必要があります。で行われた操作を のラムダ関数にmap
移動することで、最初に使用を避けることができます。map
fold
List.fold g acc (List.map (fun xxs -> myfunc f xxs) xs)
なる:
List.fold (fun state xxs -> g state (myfunc f xxs)) acc xs
次に、コードを次のように書き直すことができます(質問に表示されなかった と の両方が継続ベースの関数になっているため、継続を表す追加の引数を取ることに注意してください)f
:g
// The additional parameter 'cont' is the continuation to be called
// when the final result of myfunc is computed
let rec myfunc' f (T (x,xs)) cont =
if (List.isEmpty xs) then
// Call the 'f' function to process the last element and give it the
// current continuation (it will be called when 'f' calculates the result)
f x cont
else
// Using the continuation-based version of fold - the lambda now takes current
// element 'xxs', the accumulated state and a continuation to call
// when it finishes calculating
List.foldCont (fun xxs state cont ->
// Call 'myfunc' recursively - note, this is tail-call position now!
myfunc' f xxs (fun res ->
// In the continuation, we perform the aggregation using 'g'
// and the result is reported to the continuation that resumes
// folding the remaining elements of the list.
g state res cont)) acc xs cont
このList.foldCont
関数は の継続ベースのバージョンでfold
あり、次のように記述できます。
module List =
let rec foldCont f (state:'TState) (list:'T list) cont =
match list with
| [] -> cont state
| x::xs -> foldCont f state xs (fun state ->
f x state cont)
完全な動作例を投稿していないため、コードを実際にテストすることはできませんでしたが、動作するはずです。