モナドハンズオン前座
-
Upload
bleis-tift -
Category
Technology
-
view
9.490 -
download
0
description
Transcript of モナドハンズオン前座
モナド概論
bleis-tift
November 17 2012
モナド概論
モナドハンズオン前座発表
bleis-tift
November 17 2012
自己紹介
id:bleis-tift / @bleis
名古屋 Scala / なごやか Scala
Scalaが好きです。でも F#のほうがもーっと(ry
Microsoft MVP for Visual F#
この会の趣旨(だったもの)
モナドで躓いている人たちに、「モナドってこんな感じのものだよ」っていうのを丁寧に教える会
決して「うわ、あの人たちこわっ!」って雰囲気にならないような感じ
より多くの人が「モナドって便利!」って思ってもらえたら素敵じゃない?
どうしてこうなった・・・
対象
次のどれかに当てはまる人を一応対象とします。
何らかの静的型付けの関数型言語でプログラムが書ける
モナドを勉強して挫折したことがある
Maybeモナドくらいなら・・・
分からない所は発表中でも構わずに質問をお願いします。分かる範囲でお答えします。
モナド
モナドとは
とりあえず、
型パラメータを 1つとる型と、
>>=(バインド)演算子と、
return関数
が出てきたら「モナド」というゆるい感じからはじめます。が、しばらく出てきませんので頭の片隅に置いておいてください。
Maybeモナド
連続したnullチェックのだるさ
こんなコードはだるい。
.
連続した nullチェック
.
.
.
let a = f1 x
if a <> null then
let b = f2 a
if b <> null then
...
こんなだるいことしてるとどっかでミスる (いわゆるぬるぽ)。returnがある言語だと、returnすればいいんじゃね?ってなることもある
途中で return
.
連続した nullチェック (Scala)
.
.
.
val a = f1(x)
if (a == null)
return null
val b = f2(a)
if (b == null)
return null
...
うーん、でもやっぱりだるい。本当にやりたかったことが埋もれてしまっている。
爆ぜろリアル!
俺はこう書きたいんだ!
.
理想形 1
.
.
.
let a = f1 x
let b = f2 a
...
もしくは・・・
.
理想形 2
.
.
.
x |> f1 |> f2 |> ...
どちらにしても nullチェックなんてしたくない!!!
とりあえず
ぬるぽが起きないようにしましょう。
.
option型を定義
.
.
.
type option<’T> = None | Some of ’T
これで stringと option<string>が別の型に!
option<string>に対して stringのメソッドは直接呼び出せなくなった
シグネチャに「値が無いかもしれない」という情報を埋め込めるようになった
Scalaの場合は sealedなクラスとしてOptionを作り、Noneという case objectと Someという case classを用意
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
全然だめだ!
そこで!こんな演算子を導入してみます。
.
>>=演算子の導入
.
.
.
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の場合の処理を関数で表現すると・・・
こう!
.
>>=演算子を使って書き直し
.
.
.
f1 x >>= (fun a ->
f2 a >>= (fun b ->
...
))
関数のネストになった!
.
ネストしたパターンマッチと比べてみる (再掲)
.
.
.
match f1 x with
| Some a ->
match f2 a with
| Some b ->
...
| None -> None
| None -> None
再確認
.
>>=演算子 (再掲)
.
.
.
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ですか?
色々書き換えてみる
.
元のコード (再掲)
.
.
.
f1 x >>= (fun a ->
f2 a >>= (fun b ->
...
))
.
ネストを取り除く
.
.
.
f1 x >>= (fun a ->
f2 a) >>= (fun b ->
f3 b) >>= (fun c ->
...
)
最後の閉じかっこが増えなくなった!
色々書き換えてみる
.
元のコード (再掲)
.
.
.
f1 x >>= (fun a ->
f2 a >>= (fun b ->
...
))
.
F#の本気
.
.
.
f1 x >>= fun a ->
f2 a >>= fun b ->
f3 b >>= fun c ->
...
括弧?何それおいしいの?
色々書き換えてみる
.
元のコード (再掲)
.
.
.
f1 x >>= (fun a ->
f2 a >>= (fun b ->
...
))
.
関数を直接渡す
.
.
.
x |> f1 >>= f2 >>= ...
お・・・?
.
理想形 2にそっくりや!(再掲)
.
.
.
x |> f1 |> f2 |> ...
理想1も実現したい!
F#や ScalaやHaskellではできるんです!それぞれ、
F#・・・コンピュテーション式
Scala・・・for式
Haskell・・・do式
という (モナド用の)構文が用意されています。>>=演算子はそれぞれ、
F#・・・Bindメソッド
Scala・・・flatMapメソッド
Haskell・・・>>=演算子
に対応します。>>=演算子以外に・・
>>=演算子以外に必要なもの
F#・・・Returnメソッド
Scala・・・mapメソッドとユニットコンストラクタ
Haskell・・・return関数
が必要になります。
横道: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
1returnをそのまま提供するのが色々面倒だからっぽい?
本題に戻って理想 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
また横道: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
}
本題に戻って
さっきのmaybeを使うと・・・
.
こう書けるようになる!
.
.
.
maybe {
let! a = f1 x
let! b = f2 a
...
return 結果}
.
理想 1と比べてみる (再掲)
.
.
.
let a = f1 x
let b = f2 a
...
もうちょっと具体的な例で説明します。
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
}
ざっくりどうなっているか
maybe { } で囲まれている部分がlet!がBindの呼び出しに(後続の処理はラムダ式で包まれる)
returnがReturnの呼び出しに
変形されます。ちなみに Scalaでは、returnに相当するのは yieldで、for式の括弧の外に来ます。
こうなるわけです
.
これが (再掲)
.
.
.
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)))
この「ネストを平坦化させる」のがモナド用構文の便利な所です。
注意!
理想形に似てるからって展開はできません。
.
これは無理
.
.
.
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
なので大丈夫だとは思いますが。
ここまでのまとめ
>>=演算子でネストをフラットに>>=演算子の後ろに処理を隠す
>>=演算子は | >演算子に似ているモナド用の構文でより自然に
実はただの式変形プログラマがカスタマイズできるシンタックスシュガーてきな
Maybeモナドが何なのか分からなくても、モナド用の構文で便利に使える←大事
Stateモナド
さて、Stateモナドですよ
Maybeモナドはもっとも理解が容易なモナドの一つ
Stateモナドは理解が難しいモナドの一つ
ハンズオンで実装するにあたって、混乱しないための知識が必要
難しいかもしれませんが、数をこなせばそのうち分かります (そのためのハンズオン)
さて行きましょう!
Stateモナドとは
モナドのすべてより:
利用場面:状態を共有する必要のある一連の操作から計算を構築する
「再代入なしで、再代入と同じような挙動を実現する」とかって理解でもよい。「F#にも Scalaにも再代入あるじゃん!何に使うのさ!」ってのはとりあえず置いといてください。
再代入なしで状態の取得や更新を実現するには
関数に他の引数と一緒に「状態」も渡す
他の戻り値と一緒に「次の状態」も返すようにする
状態のやりくりを頑張る
だるそう!
実際だるい
.
自分で状態を管理する
.
.
.
let x, state1 = f1 (a, initialState)
let y, state2 = f2 (x, state1)
let z, state3 = f3 (y, state2)
...
そこで Stateモナドですよ!
Stateモナドの型
.
State型
.
.
.
// 状態を受け取って、// 値と次の状態のタプルを返す関数type State<’TState, ’T> =
’TState -> (’T * ’TState)
あれ、モナドって型パラメータは一つだったはずじゃ?→状態を表す型を固定化すればいいのさ!
バインドの後ろに何を隠すか
状態の管理を隠しましょう。
.
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()
バインドわけわかんない><。
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に渡している
バインドの中を詳しく見てみる
.
.
// 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型に
・・・
狐につままれた感じですかね実際に実装して処理を追うと理解の助けになりますので頑張ってください
使ってみる
.
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
こんなもの使えるかー!
ごもっとも
なので補助関数を定義しましょう!
.
.
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)
この補助関数を使うと・・・
こうなります!
.
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)
getとput
なんでアレで状態の取得や更新ができるの・・・?
バインドの定義と、getや putの定義を追えばわかりやすいかも
getgetとバインドの定義
.
.
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
続・get
更に展開すると
.
.
fun state ->
let x, nextState -> state, state
rest x nextState
最終的にこう
.
.
fun state -> rest state state
これは何を意味するか?xは表に出てくる値を表し、restは後続処理を表す表に出てくる値として、現在の状態を渡すことになる次の状態として、現在の状態を弄らずに渡すことになる結果、「値としては現在の状態が取得」できるし、状態もいじらない!
putputとバインドの定義
.
.
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
続・put
最後までは展開しないけど・・・
.
.
fun state ->
let x, nextState = (), newStat
rest x nextState
これは何を意味するか後続処理に値として何も渡さない (()を渡す)次の状態として、put関数に渡された値を使う結果、「指定した値で状態を更新」できるし、表に出てくる値は生成しない
ここまでのまとめ
バインドの後ろに状態の管理を隠したのがStateモナド
丁寧に読み解けばなんとなくの理解は得られる(と、思う)再代入なしで可変な状態を実現できた
補助関数がないとつらいgetと put
補助関数の動作について展開して追ってみた
まとめ?
さて
Maybeモナドと Stateモナドという異なる性質を持つ 2つのモナドを見ましたこの 2つのモナドは、
bind:Monad<’T> -> (’T -> Monad<’U>) -> Monad<’U>return: ’T -> Monad<’T>
という 2つの関数を持つという共通点しかありませんでした (実際にやる処理は全然違う)
様々なモナドが存在し、様々な bindと returnを提供しています
モナドは「何をやるか」は決めず、記法を提供するだけ
それでは、色々なモナドを実装していきましょう!
これから先のこと
モナド自体が何のか?という問いには答えていない
無理><そんなことより色んなモナド学ぼう!というスタンス気になったら調べてみるといいとは思うけど、おススメしない
色んなモナドを学ぶためのある程度の道しるべをつけておきます
その後は自分で歩けると信じて
モナドのすべて
http://www.sampou.org/haskell/a-a-monads/html/index.html
英題は「All Abouts Monads」
分かりやすいかどうかは別として、色々なモナドに触れることができる
エンコーディングは euc-jpで
モナドとモナド変換子のイメージを描いてみた
http://d.hatena.ne.jp/melpon/20111028/1319782898
モナド変換子はとりあえず置いといて・・・
各関数のイメージがよくつかめる
モナドはメタファーではない
http://eed3si9n.com/ja/monads-are-not-metaphors
たとえに頼らずに説明している
プログラムの中からモナドを見つけるための感覚つくりに
サルでもわかる IOモナド
url
http://blogs.dion.ne.jp/keis/archives/5880105.html
http://blogs.dion.ne.jp/keis/archives/5907722.html
http://blogs.dion.ne.jp/keis/archives/5984552.html
IOモナドを倒すために
モナドチュートリアル
http://www.slideshare.net/tanakh/monad-tutorial
理解できるかどうかは置いておいて、一度一通り読んでみる
最後に一つ忠告しておきます
Wikipediaは見ない方がいい