モナドハンズオン前座

60
モナド概論 bleis-tift November 17 2012

Upload: bleis-tift

Post on 04-Jun-2015

9.490 views

Category:

Technology


0 download

DESCRIPTION

わかめモナ化の発表資料です。

TRANSCRIPT

Page 1: モナドハンズオン前座

モナド概論

bleis-tift

November 17 2012

Page 2: モナドハンズオン前座

モナド概論

モナドハンズオン前座発表

bleis-tift

November 17 2012

Page 3: モナドハンズオン前座

自己紹介

id:bleis-tift / @bleis

名古屋 Scala / なごやか Scala

Scalaが好きです。でも F#のほうがもーっと(ry

Microsoft MVP for Visual F#

Page 4: モナドハンズオン前座

この会の趣旨(だったもの)

モナドで躓いている人たちに、「モナドってこんな感じのものだよ」っていうのを丁寧に教える会

決して「うわ、あの人たちこわっ!」って雰囲気にならないような感じ

より多くの人が「モナドって便利!」って思ってもらえたら素敵じゃない?

どうしてこうなった・・・

Page 5: モナドハンズオン前座

対象

次のどれかに当てはまる人を一応対象とします。

何らかの静的型付けの関数型言語でプログラムが書ける

モナドを勉強して挫折したことがある

Maybeモナドくらいなら・・・

分からない所は発表中でも構わずに質問をお願いします。分かる範囲でお答えします。

Page 6: モナドハンズオン前座

モナド

Page 7: モナドハンズオン前座

モナドとは

とりあえず、

型パラメータを 1つとる型と、

>>=(バインド)演算子と、

return関数

が出てきたら「モナド」というゆるい感じからはじめます。が、しばらく出てきませんので頭の片隅に置いておいてください。

Page 8: モナドハンズオン前座

Maybeモナド

Page 9: モナドハンズオン前座

連続したnullチェックのだるさ

こんなコードはだるい。

.

連続した nullチェック

.

.

.

let a = f1 x

if a <> null then

let b = f2 a

if b <> null then

...

こんなだるいことしてるとどっかでミスる (いわゆるぬるぽ)。returnがある言語だと、returnすればいいんじゃね?ってなることもある

Page 10: モナドハンズオン前座

途中で return

.

連続した nullチェック (Scala)

.

.

.

val a = f1(x)

if (a == null)

return null

val b = f2(a)

if (b == null)

return null

...

うーん、でもやっぱりだるい。本当にやりたかったことが埋もれてしまっている。

Page 11: モナドハンズオン前座

爆ぜろリアル!

俺はこう書きたいんだ!

.

理想形 1

.

.

.

let a = f1 x

let b = f2 a

...

もしくは・・・

.

理想形 2

.

.

.

x |> f1 |> f2 |> ...

どちらにしても nullチェックなんてしたくない!!!

Page 12: モナドハンズオン前座

とりあえず

ぬるぽが起きないようにしましょう。

.

option型を定義

.

.

.

type option<’T> = None | Some of ’T

これで stringと option<string>が別の型に!

option<string>に対して stringのメソッドは直接呼び出せなくなった

シグネチャに「値が無いかもしれない」という情報を埋め込めるようになった

Scalaの場合は sealedなクラスとしてOptionを作り、Noneという case objectと Someという case classを用意

Page 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

Page 14: モナドハンズオン前座

全然だめだ!

Page 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の場合の処理を関数で表現すると・・・

Page 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

Page 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ですか?

Page 18: モナドハンズオン前座

色々書き換えてみる

.

元のコード (再掲)

.

.

.

f1 x >>= (fun a ->

f2 a >>= (fun b ->

...

))

.

ネストを取り除く

.

.

.

f1 x >>= (fun a ->

f2 a) >>= (fun b ->

f3 b) >>= (fun c ->

...

)

最後の閉じかっこが増えなくなった!

Page 19: モナドハンズオン前座

色々書き換えてみる

.

元のコード (再掲)

.

.

.

f1 x >>= (fun a ->

f2 a >>= (fun b ->

...

))

.

F#の本気

.

.

.

f1 x >>= fun a ->

f2 a >>= fun b ->

f3 b >>= fun c ->

...

括弧?何それおいしいの?

Page 20: モナドハンズオン前座

色々書き換えてみる

.

元のコード (再掲)

.

.

.

f1 x >>= (fun a ->

f2 a >>= (fun b ->

...

))

.

関数を直接渡す

.

.

.

x |> f1 >>= f2 >>= ...

お・・・?

.

理想形 2にそっくりや!(再掲)

.

.

.

x |> f1 |> f2 |> ...

Page 21: モナドハンズオン前座

理想1も実現したい!

F#や ScalaやHaskellではできるんです!それぞれ、

F#・・・コンピュテーション式

Scala・・・for式

Haskell・・・do式

という (モナド用の)構文が用意されています。>>=演算子はそれぞれ、

F#・・・Bindメソッド

Scala・・・flatMapメソッド

Haskell・・・>>=演算子

に対応します。>>=演算子以外に・・

Page 22: モナドハンズオン前座

>>=演算子以外に必要なもの

F#・・・Returnメソッド

Scala・・・mapメソッドとユニットコンストラクタ

Haskell・・・return関数

が必要になります。

Page 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

1returnをそのまま提供するのが色々面倒だからっぽい?

Page 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

Page 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

}

Page 26: モナドハンズオン前座

本題に戻って

さっきのmaybeを使うと・・・

.

こう書けるようになる!

.

.

.

maybe {

let! a = f1 x

let! b = f2 a

...

return 結果}

.

理想 1と比べてみる (再掲)

.

.

.

let a = f1 x

let b = f2 a

...

もうちょっと具体的な例で説明します。

Page 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

}

Page 28: モナドハンズオン前座

ざっくりどうなっているか

maybe { } で囲まれている部分がlet!がBindの呼び出しに(後続の処理はラムダ式で包まれる)

returnがReturnの呼び出しに

変形されます。ちなみに Scalaでは、returnに相当するのは yieldで、for式の括弧の外に来ます。

Page 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)))

この「ネストを平坦化させる」のがモナド用構文の便利な所です。

Page 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

なので大丈夫だとは思いますが。

Page 31: モナドハンズオン前座

ここまでのまとめ

>>=演算子でネストをフラットに>>=演算子の後ろに処理を隠す

>>=演算子は | >演算子に似ているモナド用の構文でより自然に

実はただの式変形プログラマがカスタマイズできるシンタックスシュガーてきな

Maybeモナドが何なのか分からなくても、モナド用の構文で便利に使える←大事

Page 32: モナドハンズオン前座

Stateモナド

Page 33: モナドハンズオン前座

さて、Stateモナドですよ

Maybeモナドはもっとも理解が容易なモナドの一つ

Stateモナドは理解が難しいモナドの一つ

ハンズオンで実装するにあたって、混乱しないための知識が必要

難しいかもしれませんが、数をこなせばそのうち分かります (そのためのハンズオン)

さて行きましょう!

Page 34: モナドハンズオン前座

Stateモナドとは

モナドのすべてより:

利用場面:状態を共有する必要のある一連の操作から計算を構築する

「再代入なしで、再代入と同じような挙動を実現する」とかって理解でもよい。「F#にも Scalaにも再代入あるじゃん!何に使うのさ!」ってのはとりあえず置いといてください。

Page 35: モナドハンズオン前座

再代入なしで状態の取得や更新を実現するには

関数に他の引数と一緒に「状態」も渡す

他の戻り値と一緒に「次の状態」も返すようにする

状態のやりくりを頑張る

だるそう!

Page 36: モナドハンズオン前座

実際だるい

.

自分で状態を管理する

.

.

.

let x, state1 = f1 (a, initialState)

let y, state2 = f2 (x, state1)

let z, state3 = f3 (y, state2)

...

そこで Stateモナドですよ!

Page 37: モナドハンズオン前座

Stateモナドの型

.

State型

.

.

.

// 状態を受け取って、// 値と次の状態のタプルを返す関数type State<’TState, ’T> =

’TState -> (’T * ’TState)

あれ、モナドって型パラメータは一つだったはずじゃ?→状態を表す型を固定化すればいいのさ!

Page 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()

バインドわけわかんない><。

Page 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に渡している

Page 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型に

Page 41: モナドハンズオン前座

・・・

狐につままれた感じですかね実際に実装して処理を追うと理解の助けになりますので頑張ってください

Page 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

Page 43: モナドハンズオン前座

こんなもの使えるかー!

Page 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)

この補助関数を使うと・・・

Page 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)

Page 46: モナドハンズオン前座

getとput

なんでアレで状態の取得や更新ができるの・・・?

バインドの定義と、getや putの定義を追えばわかりやすいかも

Page 47: モナドハンズオン前座

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

Page 48: モナドハンズオン前座

続・get

更に展開すると

.

.

fun state ->

let x, nextState -> state, state

rest x nextState

最終的にこう

.

.

fun state -> rest state state

これは何を意味するか?xは表に出てくる値を表し、restは後続処理を表す表に出てくる値として、現在の状態を渡すことになる次の状態として、現在の状態を弄らずに渡すことになる結果、「値としては現在の状態が取得」できるし、状態もいじらない!

Page 49: モナドハンズオン前座

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

Page 50: モナドハンズオン前座

続・put

最後までは展開しないけど・・・

.

.

fun state ->

let x, nextState = (), newStat

rest x nextState

これは何を意味するか後続処理に値として何も渡さない (()を渡す)次の状態として、put関数に渡された値を使う結果、「指定した値で状態を更新」できるし、表に出てくる値は生成しない

Page 51: モナドハンズオン前座

ここまでのまとめ

バインドの後ろに状態の管理を隠したのがStateモナド

丁寧に読み解けばなんとなくの理解は得られる(と、思う)再代入なしで可変な状態を実現できた

補助関数がないとつらいgetと put

補助関数の動作について展開して追ってみた

Page 52: モナドハンズオン前座

まとめ?

Page 53: モナドハンズオン前座

さて

Maybeモナドと Stateモナドという異なる性質を持つ 2つのモナドを見ましたこの 2つのモナドは、

bind:Monad<’T> -> (’T -> Monad<’U>) -> Monad<’U>return: ’T -> Monad<’T>

という 2つの関数を持つという共通点しかありませんでした (実際にやる処理は全然違う)

様々なモナドが存在し、様々な bindと returnを提供しています

モナドは「何をやるか」は決めず、記法を提供するだけ

それでは、色々なモナドを実装していきましょう!

Page 54: モナドハンズオン前座

これから先のこと

モナド自体が何のか?という問いには答えていない

無理><そんなことより色んなモナド学ぼう!というスタンス気になったら調べてみるといいとは思うけど、おススメしない

色んなモナドを学ぶためのある程度の道しるべをつけておきます

その後は自分で歩けると信じて

Page 55: モナドハンズオン前座

モナドのすべて

http://www.sampou.org/haskell/a-a-monads/html/index.html

英題は「All Abouts Monads」

分かりやすいかどうかは別として、色々なモナドに触れることができる

エンコーディングは euc-jpで

Page 56: モナドハンズオン前座

モナドとモナド変換子のイメージを描いてみた

http://d.hatena.ne.jp/melpon/20111028/1319782898

モナド変換子はとりあえず置いといて・・・

各関数のイメージがよくつかめる

Page 57: モナドハンズオン前座

モナドはメタファーではない

http://eed3si9n.com/ja/monads-are-not-metaphors

たとえに頼らずに説明している

プログラムの中からモナドを見つけるための感覚つくりに

Page 58: モナドハンズオン前座

サルでもわかる 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モナドを倒すために

Page 59: モナドハンズオン前座

モナドチュートリアル

http://www.slideshare.net/tanakh/monad-tutorial

理解できるかどうかは置いておいて、一度一通り読んでみる

Page 60: モナドハンズオン前座

最後に一つ忠告しておきます

Wikipediaは見ない方がいい