Tomasの答えに基づいて、2つのモジュールを定義しましょう。
module Kurt =
type Gen<'a> = Gen of (int -> 'a)
let unit x = Gen (fun _ -> x)
let bind k (Gen m) =
Gen (fun n ->
let (Gen m') = k (m n)
m' n)
type GenBuilder() =
member x.Return(v) = unit v
member x.Bind(v,f) = bind f v
let gen = GenBuilder()
module Tomas =
type Gen<'a> = Gen of (int -> ('a -> unit) -> unit)
let unit x = Gen (fun _ f -> f x)
let bind k (Gen m) =
Gen (fun n f ->
m n (fun r ->
let (Gen m') = k r
m' n f))
type GenBuilder() =
member x.Return v = unit v
member x.Bind(v,f) = bind f v
let gen = GenBuilder()
少し単純化するために、元のシーケンス関数を次のように書き直してみましょう。
let rec sequence = function
| [] -> gen { return [] }
| m::ms -> gen {
let! x = m
let! xs = sequence ms
return x::xs }
これで、またはで定義されているsequence [for i in 1 .. 100000 -> unit i]
かどうかに関係なく、最後まで実行されます。問題は、定義を使用するときにスタックオーバーフローを引き起こすことではなく、呼び出しから返された関数が呼び出されたときにスタックオーバーフローを引き起こすことです。sequence
Kurt.gen
Tomas.gen
sequence
sequence
これがなぜそうなのかを理解するためsequence
に、基礎となるモナディック操作の観点からの定義を拡張してみましょう。
let rec sequence = function
| [] -> unit []
| m::ms ->
bind (fun x -> bind (fun xs -> unit (x::xs)) (sequence ms)) m
Kurt.unit
と値をインライン化しKurt.bind
、狂ったように単純化すると、
let rec sequence = function
| [] -> Kurt.Gen(fun _ -> [])
| (Kurt.Gen m)::ms ->
Kurt.Gen(fun n ->
let (Kurt.Gen ms') = sequence ms
(m n)::(ms' n))
let (Kurt.Gen f) = sequence [for i in 1 .. 1000000 -> unit i] in f 0
これで、呼び出しがスタックをオーバーフローする理由が明らかf
になりました。結果の関数のシーケンスと評価に末尾再帰以外の呼び出しが必要になるため、再帰呼び出しごとに1つのスタックフレームがあります。
代わりに、の定義にインラインTomas.unit
化して、次の簡略化されたバージョンを取得します。Tomas.bind
sequence
let rec sequence = function
| [] -> Tomas.Gen (fun _ f -> f [])
| (Tomas.Gen m)::ms ->
Tomas.Gen(fun n f ->
m n (fun r ->
let (Tomas.Gen ms') = sequence ms
ms' n (fun rs -> f (r::rs))))
このバリアントについての推論には注意が必要です。(Tomasが彼の回答で示しているように)任意に大きな入力に対してスタックを爆破しないことを経験的に検証でき、この事実を確信するために評価を段階的に進めることができます。ただし、スタックの消費量は、Gen
渡されたリスト内のインスタンスによって異なり、それ自体が末尾再帰ではない入力に対してスタックをブローする可能性があります。
// ok
let (Tomas.Gen f) = sequence [for i in 1 .. 1000000 -> unit i]
f 0 (fun list -> printfn "%i" list.Length)
// not ok...
let (Tomas.Gen f) = sequence [for i in 1 .. 1000000 -> Gen(fun _ f -> f i; printfn "%i" i)]
f 0 (fun list -> printfn "%i" list.Length)