Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

モナドハンズオン前座

11,336 views

Published on

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

Published in: Technology
  • Be the first to comment

モナドハンズオン前座

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

×