9. 連続した null チェックのだるさ
こんなコードはだるい。
連続した null チェ ック
let a = f1 x
if a <> null then
let b = f2 a
if b <> null then
...
こんなだるいことしてるとどっかでミスる (いわ
ゆるぬるぽ)。
return がある言語だと、return すればいいんじゃ
ね?ってなることもある
10. 途中で return
連続した null チェック (Scala)
val a = f1(x)
if (a == null)
return null
val b = f2(a)
if (b == null)
return null
...
うーん、でもやっぱりだるい。
本当にやりたかったことが埋もれてしまっている。
12. とりあえず
ぬるぽが起きないようにしましょう。
option 型を定義
type option<’T> = None | Some of ’T
これで string と option<string> が別の型に!
option<string> に対して string のメソッドは
直接呼び出せなくなった
シグネチャに「値が無いかもしれない」とい
う情報を埋め込めるようになった
Scala の場合は sealed なクラスとして Option
を作り、None という case object と Some とい
う case class を用意
13. option 型で書き直す
連続した null チェ ック (再掲)
let a = f1 x
if a <> null then
let b = f2 a
if b <> null then
...
各関数が option を返すように書き換え
match f1 x with
| Some a ->
match f2 a with
| Some b ->
...
| None -> None
| None -> None
15. そこで!
こんな演算子を導入してみます。
>>= 演算子の導入
let (>>=) opt (f: ’a -> option<’b>) =
match opt with
| Some x -> f x
| None -> None
ネストしたパターンマッチ (再掲)
match f1 x with
| Some a ->
match f2 a with
| Some b ->
...
| None -> None
| None -> None
Some の場合の処理を関数で表現すると・
・・
16. こう!
>>= 演算子を使って書き直し
f1 x >>= (fun a ->
f2 a >>= (fun b ->
...
))
関数のネストになった!
ネストしたパターンマッチと比べてみる (再掲)
match f1 x with
| Some a ->
match f2 a with
| Some b ->
...
| None -> None
| None -> None
17. 再確認
>>= 演算子 (再掲)
let (>>=) opt (f: ’a -> option<’b>) =
match opt with
| Some x -> f x
| None -> None
ネストしたパターンマッチ (再掲)
match f1 x with
| Some a ->
match f2 a with
| Some b ->
...
| None -> None
| None -> None
OK ですか?
18. 色々書き換えてみる
元のコード (再掲)
f1 x >>= (fun a ->
f2 a >>= (fun b ->
...
))
ネストを取り除く
f1 x >>= (fun a ->
f2 a) >>= (fun b ->
f3 b) >>= (fun c ->
...
)
最後の閉じかっこが増えなくなった!
19. 色々書き換えてみる
元のコード (再掲)
f1 x >>= (fun a ->
f2 a >>= (fun b ->
...
))
F#の本気
f1 x >>= fun a ->
f2 a >>= fun b ->
f3 b >>= fun c ->
...
括弧?何それおいしいの?
20. 色々書き換えてみる
元のコード (再掲)
f1 x >>= (fun a ->
f2 a >>= (fun b ->
...
))
関数を直接渡す
x |> f1 >>= f2 >>= ...
お・ ?
・・
理想形 2 にそっくりや!(再掲)
x |> f1 |> f2 |> ...
23. 横道:Scala の map メソッド
Option[T] に T => U な関数を適用し、
Option[U] を作るメソッド
flatMap とユニットコンストラクタがあれば
作れる
// Option[T] のメソッド (this は Option[T])
def map[U](f: T => U): Option[U] =
this.flatMap { x => Some(f(x)) }
1
にも関わらず必要なのは効率のため?
1
return をそのまま提供するのが色々面倒だからっぽい?
24. 本題に戻って
理想 1 の実現に必要なクラスを用意します。
MaybeBuilder
type MaybeBuilder() =
member this.Bind(opt, f) =
match opt with
| Some x -> f x
| None -> None
member this.Return(x) = Some x
let maybe = MaybeBuilder()
>>= 演算子の定義はこうでした。
>>= 演算子の定義 (再掲)
let (>>=) opt (f: ’a -> option<’b>) =
match opt with
| Some x -> f x
| None -> None
25. また横道:Scala だと
Option クラスにメソッドを定義することになり
ます。
def flatMap[U](f: T => Option[U]): Option[U] =
this match {
case Some(x) => f(x)
case None => None
}
def map[U](f: T => U): Option[U] =
this match {
case Some(x) => Some(f(x))
case None => None
}
26. 本題に戻って
さっきの maybe を使うと・
・・
こう書けるようになる!
maybe {
let! a = f1 x
let! b = f2 a
...
return 結果
}
理想 1 と比べてみる (再掲)
let a = f1 x
let b = f2 a
...
もうちょっと具体的な例で説明します。
27. http://d.hatena.ne.jp/mzp/20110205/monad
「db という Map に格納されている”x”と”y”を加
算する」
理想
let db = Map.ofList [("x", 1); ("y", 2); ("z", 3)]
let result =
let x = db |> Map.find "x"
let y = db |> Map.find "y"
x + y
実際
let result = maybe {
let! x = db |> Map.tryFind "x"
let! y = db |> Map.tryFind "y"
return x + y
}
29. こうなるわけです
これが (再掲)
let result = maybe {
let! x = db |> Map.tryFind "x"
let! y = db |> Map.tryFind "y"
return x + y
}
こう変形される
let result =
maybe.Bind(db |> Map.tryFind "x", fun x ->
maybe.Bind(db |> Map.tryFind "y", fun y ->
maybe.Return(x + y)))
この「ネストを平坦化させる」のがモナド用構文
の便利な所です。
30. 注意!
理想形に似てるからって展開はできません。
これは無理
let result = maybe {
return (db |> Map.tryFind "x") +
(db |> Map.tryFind "y")
}
まぁScala の場合は
Scala 版
val result = for {
x <- db.get("x")
y <- db.get("y")
} yield x + y
なので大丈夫だとは思いますが。
36. 実際だるい
自分で状態を管理する
let x, state1 = f1 (a, initialState)
let y, state2 = f2 (x, state1)
let z, state3 = f3 (y, state2)
...
そこで State モナドですよ!
37. State モナドの型
State 型
// 状態を受け取って、
// 値と次の状態のタプルを返す関数
type State<’TState, ’T> =
’TState -> (’T * ’TState)
あれ、モナドって型パラメータは一つだったはず
じゃ?
→状態を表す型を固定化すればいいのさ!
38. バインドの後ろに何を隠すか
状態の管理を隠しましょう。
StateBuilder
type StateBuilder () =
member this.Bind(stateM, rest) =
fun state ->
let x, nextState = stateM state
rest x nextState
member this.Return(x) =
fun state -> (x, state)
let state = StateBuilder()
バインドわけわかんない><。
39. Maybe モナドとの共通点を探す
Maybe モナドのバインド
// type Option<’T> = None | Some of ’T
member this.Bind(opt, rest) =
match opt with
| Some x -> rest x
| None -> None
State モナドのバインド
// type State<’TState, ’T> = ’TState -> (’T * ’TState)
member this.Bind(stateM, rest) =
fun state ->
let x, nextState = stateM state
rest x nextState
Bind メソッドの型 (モナドと関数を受け取り、モナドを返す)
rest の型 (モナドの中の値を受け取り、モナドを返す)
取り出した値を rest に渡している
40. バインドの中を詳しく見てみる
// type State<’TState, ’T> = ’TState -> (’T * ’TState)
member this.Bind(stateM, rest) =
fun state ->
let x, nextState = stateM state
rest x nextState
stateM は State 型 (なので、関数)
stateM に状態を渡すと、値 x と次の状態 nextState のタプ
ルが取得できる
先ほどのだるいコードはここに相当
rest は後続処理を表す関数で、戻り値は State 型 (関数)
rest は元々引数を 1 つ取るため、カリー化された 2 引数関
数とみなせる
rest x をそのまま Bind の戻り値として返しただけだと「次の状
態」が伝播できない
rest x に「次の状態」を渡してしまい、全体をラムダ式で包み
State 型に
42. 使ってみる
state を使ってみる
let result = state {
let! initVal =
fun initStat -> (initStat, initStat)
let x = initVal + 1
do! fun _ -> ((), x * 2)
return x
}
let res1 = result 0 // => (1, 2)
let res2 = result 10 // => (11, 22)
バインドの定義はこちら
member this.Bind(stateM, rest) =
fun state ->
let x, nextState = stateM state
rest x nextState
44. ごもっとも
なので補助関数を定義しましょう!
let result = state {
let! initVal =
fun initStat -> (initStat, initStat) // 状態の取得
let x = initVal + 1
do! fun _ -> ((), x * 2) // 状態の設定
return x
}
状態の取得と更新を関数化します。
補助関数
let get = fun stat -> (stat, stat)
let put newStat = fun _ -> ((), newStat)
この補助関数を使うと・
・・
45. こうなります!
state 完全版
let result = state {
let! initVal = get
let x = initVal + 1
do! put (x * 2)
return x
}
補助関数を定義する前とは大違い。
補助関数定義前 (再掲)
let result = state {
let! initVal =
fun initStat -> (initStat, initStat)
let x = initVal + 1
do! fun _ -> ((), x * 2)
return x
}
let res1 = result 0 // => (1, 2)
let res2 = result 10 // => (11, 22)
47. get
get とバインドの定義
let get = fun stat -> (stat, stat)
member this.Bind(stateM, rest) =
fun state ->
let x, nextState = stateM state
rest x nextState
stateM が get だった場合
fun state ->
let x, nextState = (fun stat -> (stat, stat)) state
rest x nextState
現在の状態 (state) を弄らずに値と次の状態として使う
fun state ->
let x, nextState -> state, state
rest x nextState
48. 続・get
更に展開すると
fun state ->
let x, nextState -> state, state
rest x nextState
最終的にこう
fun state -> rest state state
これは何を意味するか?
x は表に出てくる値を表し、rest は後続処理を
表す
表に出てくる値として、現在の状態を渡すこと
になる
次の状態として、現在の状態を弄らずに渡すこ
とになる
結果、 「値としては現在の状態が取得」できる
し、状態もいじらない!
49. put
put とバインドの定義
let put newStat = fun _ -> ((), newStat)
member this.Bind(stateM, rest) =
fun state ->
let x, nextState = stateM state
rest x nextState
stateM が put だった場合
fun state ->
let x, nextState = (fun _ -> ((), newStat)) state
rest x nextState
現在の状態 (state) を捨てている
fun state ->
let x, nextState = (), newStat
rest x nextState
50. 続・put
最後までは展開しないけど・
・・
fun state ->
let x, nextState = (), newStat
rest x nextState
これは何を意味するか
後続処理に値として何も渡さない (() を渡す)
次の状態として、put 関数に渡された値を使う
結果、「指定した値で状態を更新」できるし、表
に出てくる値は生成しない