ビルダークラスの作り方・使い方

実践F#を読んでもよくわからなかったのでサンプルをさらに簡単にした奴をメモ。

type OwnBuilder() =
    member x.Return(r) = 
        printfn "Return (%A)" r
        r
    member x.Delay(f) = 
        printfn "Delay (%A)" f
        fun () -> f()
;;
let ownBuilder = new OwnBuilder();;
let myExp = ownBuilder{
    let x = 100
    let y = 1
    return x + y
};;

として実行した結果は

type OwnBuilder =
  class
    new : unit -> OwnBuilder
    member Delay : f:(unit -> 'a) -> (unit -> 'a)
    member Return : r:'b -> 'b
  end
val ownBuilder : OwnBuilder
Delay (<fun:myExp@31-5>)
val myExp : (unit -> int)

となって、Delayが自動的に実行されてその結果がmyExpへと入る。この時の引数fはownBuilder{...}の...の箇所が評価されてクロージャー(この場合 fun() -> f())として入ってくる。この結果を取得したいときは、

> myExp ();;
Return (101)
val it : int = 101

として、これを評価してやるとReturnメソッドが実行されてその結果の101を取得できる。


さらにこれをもうちょっと書き換えて

type OwnBuilder() =
    member x.Return(r) = 
        printfn "Return (%A)" r
        r
    member x.Delay(f) = 
        printfn "Delay (%A)" f
        fun () -> f()
    member x.Bind(p, rest) =
        printfn "Bind (%A, %A)" p rest
        rest p
;;
let ownBuilder = new OwnBuilder();;
let myExp = ownBuilder{
    let x = 100
    let! y = 1
    return x + y
};;

としてやる。ここでlet!はBindメソッドへと変換されるので、Bindメソッドを新たに定義している。これを実行すると

type OwnBuilder =
  class
    new : unit -> OwnBuilder
    member Bind : p:'a * rest:('a -> 'b) -> 'b
    member Delay : f:(unit -> 'c) -> (unit -> 'c)
    member Return : r:'d -> 'd
  end
val ownBuilder : OwnBuilder
Delay (<fun:myExp@34-6>)
val myExp : (unit -> int)

Bindメソッドが追加されている以外はあまり変わったように見えない。

> myExp ();;
Bind (1, <fun:myExp@36-7>)
Return (101)
val it : int = 101

としてここでBindメソッドが実行される。