bleis-tift
コンピュテーション式は、その種類に応じて、 モナド、モノイド、モナド変換子、および アプリカティブファンクターを表現する方法と 考えることができます。
let
式を考えてみましょうlet x = 10
x * 2
これを、let
構文を使わずに実現しようとすると?
let
は変数の導入なので、ラムダ式で代替可能
// 元のコード
// let x = 10
// x * 2
10
|> fun x -> x * 2
// 元のコード
// let x = 10
// x * 2
X.LetBinding(10, fun x -> x * 2)
このようにできれば、 元のコードの意味をカスタマイズできる →基本的なコンピュテーション式の考え方
つまり、F#の構文の意味がカスタマイズできる!
do
for-yield
// コンピュテーション式
x {
let! a = f ()
let! b = g ()
return a * b
}
// 展開結果(例)
x.Bind(f (), fun a ->
x.Bind(g (), fun b ->
b.Return(a * b)))
type X () =
member _.Bind(x, f) = f x
member _.Return(x) = x
let x = X ()
option
の値を取り出しての計算option
の値を取り出しての計算match o1 with
| Some a ->
match o2 with
| Some b ->
Some (a + b)
| None -> None
| None -> None
match
のネストが面倒!
type B () =
member _.Bind(x, f) =
// Option.bind f x でも可
match x with
| Some a -> f a
| None -> None
member _.Return(x) = Some x
let x = B ()
x {
let! a = o1
let! b = o2
return a + b
}
// オリジナル
match o1 with
| Some a ->
match o2 with
| Some b ->
Some (a + b)
| None -> None
| None -> None
// コンピュテーション式
b {
let! a = o1
let! b = o2
return a + b
}
// de-sugar
b.Bind(o1, fun a ->
b.Bind(o2, fun b ->
b.Return(a + b)))
Delay
変換Delay
メソッドを持っている場合、Delay
変換が行われる。b.Delay(fun () -> ...)
で囲むDelay
に渡された関数の起動を選択できるDelay
は他の変換で使われることも
Delay
変換type B () =
member _.Delay(f) = f // 関数を実行しない
member _.Bind(x, f) = Option.bind f x
member _.Return(x) = Some x
let x = B ()
x {
let! a = o1
let! b = o2
return a + b
}
// こうなる(全体の型は、unitを受け取る関数)
x.Delay(fun () ->
x.Bind(o1, fun a ->
x.Bind(o2, fun b ->
x.Return(a + b))))
Run
変換Run
メソッドを持っている場合、Run
変換が行われる。b.Run(...)
で囲むDelay
よりも外側になるため、Delay
変換でRun
変換type B () =
member _.Run(f) = f () // Delayした関数をここで実行
member _.Delay(f) = f
member _.Bind(x, f) = Option.bind f x
member _.Return(x) = Some x
let x = B ()
x {
let! a = o1
let! b = o2
return a + b
}
// こうなる
x.Run(
x.Delay(fun () ->
x.Bind(o1, fun a ->
x.Bind(o2, fun b ->
x.Return(a + b)))))
Run
の応用type B (v: 'a) =
member _.Run(f) = f () |> Option.defaultValue v
member _.Delay(f) = f
member _.Bind(x, f) = Option.bind f x
member _.Return(x) = Some x
let x v = B ()
// o1かo2がNoneの場合、-1
x -1 {
let! a = o1
let! b = o2
return a + b
}
Zero
変換else
のない if
の else
側DefaultValue
の付いたビルダーでの do!
b { () }
で出てくる
b.Run(b.Delay(fun () -> b.Zero() ))
While
変換while e do ce
に対する変換
while
式に対する変換b.While((fun () -> e), b.Delay(fun () -> ceの変換結果))
Delay
が出てくるWhile
変換type B () =
member _.Run(f) = f ()
member _.Delay(f) = f
member this.While(cond, body) =
if not (cond ()) then None
else
match body () with
| Some _ -> this.While(cond, body)
| None -> None
member _.Bind(x, f) = Option.bind f x
member _.Return(x) = Some x
member _.Zero() = None
let x = B ()
x {
let mutable i = 0
while i < 5 do
printfn "loop"
i <- i + 1
return 10
} |> printfn "%A"
While
変換While
変換type B () =
member _.Run(f) = f ()
member _.Delay(f) = f
member this.While(cond, body) =
if not (cond ()) then None // 最終的には、ここに来て全体としてNoneが返る
else
match body () with
| Some _ -> this.While(cond, body) // body ()の結果は捨てる
| None -> None
member _.Bind(x, f) = Option.bind f x
member _.Return(x) = Some x
member _.Zero() = None
let x = B ()
x {
let mutable i = 0
while i < 5 do
printfn "loop"
i <- i + 1
return 10
}
// ざっくりこうなる
x.Run(x.Delay(fun () ->
let mutable i = 0
b.While((fun () -> i < 5), b.Delay(fun () ->
printfn "loop"
i <- i + 1
b.Return(10)))))
return
で return
するには・・・Combine
変換ce1; ce2
に対する変換
b.Combine(ce1の変換結果, b.Delay(fun () -> ce2の変換結果))
Delay
が出てくるWhile
にはほぼ必須While
にはほぼ必須?x {
let mutable i = 0
while i < 5 do
printfn "loop"
i <- i + 1
// 気持ち悪いけどいったん目をつぶってください!
return -1
// Combineがないと、whileの後にコードが書けない
return 10
}
Combine
変換type B () =
member _.Run(f) = f ()
member _.Delay(f) = f
member this.While(cond, body) =
if not (cond ()) then Some (Unchecked.defaultof<_>)
else this.Combine(body (), fun () -> this.While(cond, body))
member _.Combine(x, f) =
match x with
| Some _ -> f ()
| None -> None
member _.Bind(x, f) = Option.bind f x
member _.Return(x) = Some x
member _.Zero() = None
let x = B ()
x {
let mutable i = 0
while i < 5 do
printfn "loop"
i <- i + 1
return -1
return 10
} |> printfn "%A"
return
で return
するtype FlowControl = Break | Continue // 実行を止めるか、続けるか
type B () =
member _.Run(f) = f () |> fst
member _.Delay(f) = f
member this.While(cond, body) =
if not (cond ()) then this.Zero()
else this.Combine(body (), fun () -> this.While(cond, body))
member _.OldCombine(x, f) =
match x with
| Some _ -> f ()
| None -> None
member _.Combine(first, rest) =
// xだけではなく、FlowControlも見る
// Breakだったら、xがNoneだったとしても続けない
// -> returnでreturnできるようになる
match first with
| x, Break
| (Some _ as x), Continue -> (x, Break)
| None, Continue -> rest ()
member _.Bind(x, f) = (Option.bind (f >> fst) x, Continue)
member _.Return(x) = (Some x, Break)
member _.Zero() = (None, Continue)
type FlowControl = Break | Continue
type B () =
member _.Run(f) = f () |> fst
member _.Delay(f) = f
member this.While(cond, body) =
if not (cond ()) then this.Zero()
else this.Combine(body (), fun () -> this.While(cond, body))
member _.Combine(first, rest) =
match first with
| x, Break
| (Some _ as x), Continue -> (x, Break)
| None, Continue -> rest ()
member _.Bind(x, f) = (Option.bind (f >> fst) x, Continue)
member _.Return(x) = (Some x, Break)
member _.Zero() = (None, Continue)
let x = B ()
x {
let mutable i = 0
while i < 5 do
printfn "loop"
i <- i + 1
return -1
return 10
} |> printfn "%A"
return
で return
できた!type FlowControl = Break | Continue
type B () =
member _.Run(f) = f () |> fst
member _.Delay(f) = f
member this.While(cond, body) =
if not (cond ()) then this.Zero()
else this.Combine(body (), fun () -> this.While(cond, body))
member _.Combine(first, rest) =
match first with
| x, Break
| (Some _ as x), Continue -> (x, Break)
| None, Continue -> rest ()
member _.Bind(x, f) = (Option.bind (f >> fst) x, Continue)
member _.Return(x) = (Some x, Break)
member _.Zero() = (None, Continue)
let x = B ()
x {
let mutable i = 0
while i < 5 do
printfn "loop"
i <- i + 1
do! None // return -1をdo!にした
// 実際には、unit optionを返す関数が書いてあると思ってください
return 10
} |> printfn "%A"
do!
の問題do! e
は let! () = e in b.Return()
unit option
が出てくるDefaultValue
属性Zero
に DefaultValue
属性を追加するとb.Zero()
の型は 'a option
type FlowControl = Break | Continue
type B () =
member _.Run(f) = f () |> fst
member _.Delay(f) = f
member this.While(cond, body) =
if not (cond ()) then this.Zero()
else this.Combine(body (), fun () -> this.While(cond, body))
member _.Combine(first, rest) =
match first with
| x, Break
| (Some _ as x), Continue -> (x, Break)
| None, Continue -> rest ()
member _.Bind(x, f) = (Option.bind (f >> fst) x, Continue)
member _.Return(x) = (Some x, Break)
[<DefaultValue>]
member _.Zero() = (None, Continue)
let x = B ()
x {
let mutable i = 0
while i < 5 do
printfn "loop"
i <- i + 1
do! None
return 10
} |> printfn "%A"
return
で return
する(別解)type B () =
// 初期継続を渡して結果を取り出す
member _.Run(f) = f () id
member _.Delay(f) = f
member this.While(cond, body) =
if not (cond ()) then this.Zero()
else this.Combine(body (), fun () -> this.While(cond, body))
member _.Combine(first, rest) =
// firstの処理を捨てて、後続処理を起動
fun k -> first (fun _ -> rest () k)
member this.Bind(x, f) =
match x with Some x -> f x | None -> this.Zero
// returnでは、継続を捨てて、xを返す(後続の処理を実行しない)
member _.Return(x) = fun _ -> x
// zeroでは、継続を起動する(後続の処理を実行する)
[<DefaultValue>]
member _.Zero() = fun k -> k Unchecked.defaultof<_>
let x = B ()
x {
let mutable i = 0
while i < 5 do
printfn "loop"
i <- i + 1
return -1
return 10
} |> printfn "%A"
if
とか use!
とかその他諸々return
で return
する
Zero
には DefaultValue
を!