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.

C#でわかる こわくないMonad

11,729 views

Published on

C#でわかる こわくないMonad

2017.5.27 岐阜Sharp #gifsharp #fsharp

Published in: Software

C#でわかる こわくないMonad

  1. 1. C#でわかる こわくないM 2017.5.27 GIFSHARP #1 KOUJI MATSUI (@KEKYO2)
  2. 2. Kouji Matsui - kekyo • NAGOYA city, AICHI pref., JP • Twitter – @kekyo2 / Facebook • ux-spiral corporation • Microsoft Most Valuable Professional VS and DevTech 2015- • Certified Scrum master / Scrum product owner • Center CLR organizer. • .NET/C#/F#/IL/metaprogramming or like… • Bike rider
  3. 3. はじめに •ずっと思考していたことを、どうやって自分 の言葉で表現するかを考えていた。今日はそ のアウトプットです。 •C#書ける人にリーチできるように考えました。 •本日が「岐阜Sharp」であることは、もちろん 承知しております ☺
  4. 4. Agenda 事の始まり Nullを安全になんとかしたい ネストしたNullの安全な処理 名前を変える 処理の連鎖 LINQクエリ 岐阜 まとめ
  5. 5. 事の始まり
  6. 6. 事の始まり
  7. 7. 事の始まり
  8. 8. 事の始まり 分かりやすい課題だし、 ここからやるのが良かろう… ※Nullの議論や考察の深掘りは、それだけで沼であり、本題か ら外れるのでここでは扱いません。例えば、.NETのNullable<T> はどうなのかとか。
  9. 9. Agenda 事の始まり Nullを安全になんとかしたい ネストしたNullの安全な処理 名前を変える 処理の連鎖 LINQクエリ 岐阜 まとめ
  10. 10. Nullを安全になんとかしたい 例がアレだが… ジェネリックじゃない辞書
  11. 11. Nullを安全になんとかしたい
  12. 12. Nullを安全になんとかしたい .NET Frameworkの進化: • Dictionary<TKey, TValue> : .NET 2.0で追加された。 • bool TryGetValue(TKey key, out TValue value) boolの判定を強制させることで、 失敗についてコード化させる。 が、モヤモヤする…
  13. 13. Nullを安全になんとかしたい 値が存在する場合だけ、コールバック関数を実行したらどうか。 こういうのを 作っておき…
  14. 14. Nullを安全になんとかしたい 値が存在する場合だけ、コールバック関数を実行したらどうか。 値が存在しない場合は 単に無視され実行されない
  15. 15. 値を安全に操作したい 辞書の例を一般化して、 任意の参照型インスタンスを 安全に操作したい。 入れ物(ValueHolder<T>)に 入れておき、Nullチェックする
  16. 16. 値を安全に操作したい 値がnullの場合は 無視される
  17. 17. 値を安全に操作したい これだと一回の操作ですべてが終わるので、ありがたみがない。 実際には値に対して複数の処理を連鎖的に実行したいはず。そこで: 戻り値を返す関数 を指定できる 関数を実行して 戻り値を返す
  18. 18. 値を安全に操作したい 安全に操作できる 何か違う… 何でNullを手動で判定 しているんだ
  19. 19. TryExecuteの戻り値はT型なので、そこから先はNull検査が必要。 では、その値をValueHolder<T>に入れれば良いのでは? 値を安全に操作したい 処理が連鎖出来るが、 いちいち入れ直す 必要がある モヤる
  20. 20. 値を安全に操作したい ValueHolder<T>で ラップして返す ターミネーション 処理用
  21. 21. 値を安全に操作したい 実行される 実行されない
  22. 22. 値を安全に操作したい 途中からNullになると 以降は安全に無視
  23. 23. 値を安全に操作したい こんなユーティリティ を作っておく
  24. 24. 値を安全に操作したい 定義が楽になる
  25. 25. Agenda 事の始まり Nullを安全になんとかしたい ネストしたNullの安全な処理 名前を変える 処理の連鎖 LINQクエリ 岐阜 まとめ
  26. 26. ネストしたNullの安全な処理 関数内でもValueHolder<T>を使いたいかもしれない: Func<T, T>
  27. 27. ネストしたNullの安全な処理 Func<T, T>から Func<T, ValueHolder<T>>に変更 もうラップしなくても 良くなった
  28. 28. ネストしたNullの安全な処理 今度はこっちがTをそのまま 返しているのでエラー
  29. 29. ネストしたNullの安全な処理 Someを使ってラップして返すと ValueHolder<T>となって辻褄が合う
  30. 30. ネストしたNullの安全な処理 我々は、これを一般的に「Option型」呼んでいます:
  31. 31. Agenda 事の始まり Nullを安全になんとかしたい ネストしたNullの安全な処理 名前を変える 処理の連鎖 LINQクエリ 岐阜 まとめ
  32. 32. 名前を変える ちょっと名前に馴染みが薄 いかもしれませんが: •“TryExecute”を”Bind” •”Some”を”Return” に変えます。
  33. 33. 名前を変える これが、 「Optionモナド」 です。 ※あるいはMaybeモナドと言う場合もあります。 ※ツッコミが入りそうだから、ちょっとまって♡
  34. 34. モナドを構成するもの モナドは、Optionだけではなく、様々な種類のものがありますが、 ある構造を「モナド」と呼ぶには、以下の構造を持っている必要が あります: • 型構築子 : 値の型をジェネリックとして受け取ることが出来る、 Option<T>型そのものの事です(.NET的に言うなら、オープンジェ ネリック型)。 • return関数 : Return関数の事です。Tの値をOption<T>に変換します • bind関数 : Bind関数の事です。Tの値を引数に取る関数を実行し、 Option<U>の値を返します Wikipedia [モナド(プログラミング)]
  35. 35. モナドを構成するもの 名称がぶれる事がある(returnをunitと呼ぶ場合など)のは、 処理系によって呼称がバラバラだからですが、モナドらしい構 造を持っていれば、名称自体は重要ではありません。 • Wikipediaでは、returnをunitとして説明しています。 • returnはHaskellのreturnから来ています。 • bindはflatMapと呼ぶ場合もあります。 ただし、会話するときには相手に通じないかもしれないので 注意したほうが良いでしょう。
  36. 36. モナドを構成するもの 名称以外にも、「モナドである」と言うには、以下の規則も 備えている必要があります: 1. return関数をそのままbind関数に適用すると、元の値のま まとなる: 要するにそのまま ラップして返している
  37. 37. モナドを構成するもの 「 return関数をそのままbind関数に適用」 を素直に書くと、より分かりやすい
  38. 38. モナドを構成するもの 2. ふたつの関数を続けてbindするのは、これらの関数から決 まるひとつの関数を bind することに等しい:
  39. 39. モナドを構成するもの “DEF”と”GHI”を計算する式を「先に」bindして、そ の式を”ABC”のOptionにbindしている: (A bind B) bind C ←同じ→ A bind (B bind C)
  40. 40. モナドを構成するもの 上記の規則のことを 「モナド則」 と言います。 ※一見してモナドのような形をした型があっても、この規則に合 致しない場合は、その型はモナドではありません。
  41. 41. Bindを正確に実装 ここまで述べたOption<T>.Bindは、実は正確ではありません。 Option<T>を返す関数(?) 違う型(Option<CultureInfo>)を返し たくても、Tがstringなのでエラー
  42. 42. Bindを正確に実装 UをジェネリックとしてOption<U>を 返す関数にする。 関数がOption<T>を返しても正しく マッチする。 • bind関数 : Bind関数の事です。Tの値を引数に取る関数を実行し、 Option<U>の値を返します
  43. 43. その他 “TryExecute”に対応するものを調べたところ、”Match”と呼称す ることがありました。 これについてはまた後で取り上げますが、とりあえず以降の サンプルはMatchと表記します。
  44. 44. Agenda 事の始まり Nullを安全になんとかしたい ネストしたNullの安全な処理 名前を変える 処理の連鎖 LINQクエリ 岐阜 まとめ
  45. 45. 処理の連鎖 複雑な計算処理を安全に行おうとすると、Bindがネストするこ とがあたりまえになります: すべての値が存在する場合だけ、 合計を計算する
  46. 46. 処理の連鎖 実はこの構造、驚くべきことにLINQのSelectManyにそっくりです: すべての値が存在する場合だけ、 合計を計算する
  47. 47. 処理の連鎖 LINQで異なるところは: • Return(value)がnew[] { value } // 要素1個だけの配列の生成 • Bind()がSelectMany() // ネストしたリストのアンループ です。 ※最後の出力はMatchがないので、foreachで出力しています。 ※bindがflatMapと呼ばれる事から、SelectManyに近しい感じもします。
  48. 48. 処理の連鎖 意味を考えてみると: 1. 要素1個だけの配列: 配列のインスタンス(int[])は常に存在し、要素が1個存在するか 又は存在しないか。 → 要素が1個存在する場合だけ処理 ≒ Nullではない場合だけ処理 Value Null [1] [0]
  49. 49. 処理の連鎖 2. SelectManyで配列を返す: 結果が1個だけ格納された、又は要素が格納されていない配列のイ ンスタンスを返すことで、結果がNullかどうかを間接的に表現 3. 最終結果はIEnumerable<int>だけど、要素が1個存在するか又は存在 しないか、のどちらかで表現される。
  50. 50. 処理の連鎖 SelectManyに渡す関数のことを「継続」、このような形式を「継 続渡し」と呼びます: 継続となるラムダ式が3重にネストしている 継続はコールバックとみなせる
  51. 51. Agenda 事の始まり Nullを安全になんとかしたい ネストしたNullの安全な処理 名前を変える 処理の連鎖 LINQクエリ 岐阜 まとめ
  52. 52. LINQクエリ構文 LINQと構造がそっくりということは、Option<T>もわざとLINQっぽ く定義することで、LINQのクエリ構文に対応できます: Bindに対応するSelectManyの実装 (邪魔なので)拡張メソッドで定義 引数が多少違うのは、LINQクエリ構文 を効率よく実行するためで、 Bindの呼び出し回数を削減する
  53. 53. LINQクエリ構文 すると、このようにシンプルに書けます: ここらへんがなんとなくbind bindが全て成功(有効な値) すると、計算してreturn
  54. 54. LINQクエリ構文 このクエリ構文は、シンプルに短く書けるものの、意味が分かり づらいことが問題です。 • fromがbindで、selectがreturnとは、想像しにくい。 つまり、LINQはあくまでコレクション(シーケンス)に対する操 作である(だからSQLっぽいキーワード)のに、その構文を全く別 の用途に応用しているからです。 ※私自身は、このように書けることにあまりメリットを見いだせ ていません…
  55. 55. LINQクエリ構文 ですが、なんとなく LINQがモナドの一種のように見える と言うのは分かりましたか? ※断定するのは去年あたりに自信がなくなって以来解決していな いので、やめておきます
  56. 56. ところでこの会は 岐阜Sharp (giFSharp) でしたね?
  57. 57. Agenda 事の始まり Nullを安全になんとかしたい ネストしたNullの安全な処理 名前を変える 処理の連鎖 LINQクエリ 岐阜 まとめ
  58. 58. F#でのモナド 基本的に、C#で見せた構造と変わりません。 (サンプルコードはC#に最適化してありますが) ここでは、 「なぜC#ではなくF#を使いたいか?」 にフォーカスします。
  59. 59. F#でのOptionモナド F#でOptionモナドを使ってみます。F#標準のOption型と被るので ”Optional”と変更していますが、C#で書いたクラスをそのまま使えます: 書き方もC#とほとんど同じ
  60. 60. F#でのLINQ F#でのLINQも書き直して対比させてみます: 書き方もC#とほとんど同じ IEnumerable<T>の共変性を考慮した 型推論が難しいので、ここだけ補助
  61. 61. F#でのシーケンス F#ではLINQ演算子を直接使うことはあまりなく、代わりに シーケンス(Seq)の演算子を使います: SelectMany (Bind) は Seq.collectに対応します Seqなら型推論出来るので、補助は不要
  62. 62. F#でのシーケンス しかし、シーケンスを使うなら、もっと良い方法があります。 シーケンス式です: ここらへんがSeq.collect (bind) bindが全て成功(有効な値) すると、計算してreturn C#でのLINQクエリ構文と そっくりです
  63. 63. F#でのシーケンス ここではシーケンス式自体はあまり掘り下げませんが、F#はこの 「ブロックで囲まれた式」をビルトインで定義しています: •シーケンス向き(Seq<T>) : seq { … } •非同期処理向き(Async<T>) : async { … } •DBクエリ向き(IQueryable<T>) : query { … } そして、これらビルトインワークフロー以外にも: •独自の計算定義 : hogehoge { … } → コンピュテーション式
  64. 64. コンピュテーション式の導入 Optionalをコンピュテーション式で使えるようにします: 「ビルダークラス」を定義します。 最低限、”Return”と”Bind”を定義しますが、 既存のOptionalに転送するだけです このクラスのインスタンスを ”optional”と命名しておきます
  65. 65. コンピュテーション式の導入 OptionalBuilderとそのインスタンス”optional”を見えるスコープ に配置しておけば: optionalコンピュテーションブロックが 使用可能に!! let! (let-bang) でbindが実行される。 値が無効ならそれ以上評価されない (Optional.Bindが無視する) 自然で穏当な式表記 optionalで生成されたインスタンスは Optional<T>そのもの
  66. 66. コンピュテーション式の導入 C# LINQクエリ構文 F# カスタムコンピュテーション式 (&中身はOptionモナド)
  67. 67. コンピュテーション式の導入 コンピュテーション式の利点: • C# LINQクエリ構文と比べ、より自然で適した文法にしやすい。 使用可能なキーワードが多い。 let, do, return, for..do, try..finally, use, yieldなど、大体網羅出来る • ビルトインワークフロー群と同じ手法で拡張でき、LINQの制約 に縛られない。 • バックエンドはほぼモナドそのまま。 「Computation Expressions」 https://docs.microsoft.com/en- us/dotnet/fsharp/language-reference/computation-expressions
  68. 68. コンピュテーション式の導入 コンピュテーション式の欠点: • F#でしか使えない ☺
  69. 69. その他 ”Match”の由来が何かを説明するのを忘れていました。 F#には「判別共用体」があります。これを使って強力なパター ンマッチングが出来るのですが: F#のOption型(判別共用体) Some: 任意の型の値を保持 None: 値を保持しない パターンマッチング: OptionにはSomeかNoneしかありえない →網羅性の自動検査が出来る
  70. 70. その他 値が存在する・値が存在しない、の両方のパターンの処理を 書かせるため、Matchと呼称していると思われます。 値が存在する場合と存在しない場合の 両方の関数を必ず書かせることで 網羅性を担保する
  71. 71. Agenda 事の始まり Nullを安全になんとかしたい ネストしたNullの安全な処理 名前を変える 処理の連鎖 LINQクエリ 岐阜 まとめ
  72. 72. モナドとは? モナドとは: モナドは計算を表現する構造であり、計算ステップの列から なる。つまり、型がモナド構造をもつというのは、命令を繋げ るやり方、言い換えるとその型をもつ関数をネストさせる規則 が定まっていることをいう。 Wikipedia [モナド (プログラミング)]
  73. 73. モナドとは? C#での、モナドの適用動機: • 正直、あまりない(だから知識共有されない?)。 • LINQの演算子がモナドっぽいけど、普段意識することはない。 • 文(Statement : voidな手続き)とは相性が悪い。Bindに渡す関数が 値を返せない。 F#での、モナドの適用動機: • 関数型プログラミング言語では一般的に使われる概念(?)。全て の式が値を返すので、自然に導入できる。 • コンピュテーション式で自然に拡張・記述しやすく出来る
  74. 74. モナドはデザインパターンか? モナドがデザインパターンと呼べるかどうかわかりませんが: デザインパターン(または設計パターン)とは、過去のソフ トウェア設計者が発見し編み出した設計ノウハウを蓄積し、名 前をつけ、再利用しやすいように特定の規約に従ってカタログ 化したものである。 Wikipedia [デザインパターン]
  75. 75. モナドはプログラミングパラダイムか? モナドがプログラミングパラダイムと呼べるかどうかわかり ませんが: プログラミングパラダイムは、プログラマにプログラムの見 方を与えるものと言える。たとえば、オブジェクト指向プログ ラミングにおいて、プログラムとはオブジェクトをつくりそれ を管理するものである。関数型プログラミングにおいては、状 態を持たない関数の評価の連続である。 Wikipedia [プログラミングパラダイム]
  76. 76. 謝辞 • 「モナドの脅威」 matarillo.com http://matarillo.com/general/monads.php • 「もしC#プログラマーがMaybeモナドを実装したら」 gab_km http://blog.livedoor.jp/gab_km/archives/1361759.html • 「Optionに見るコンピュテーション式のつくり方」 bleis-tift http://bleis-tift.hatenablog.com/entry/how-to-make- computation-expression
  77. 77. 謝辞
  78. 78. Thanks join! The implementations --> GitHub: CSharpMonadic ◦ https://github.com/kekyo/CSharpMonadic/ My blog ◦ http://www.kekyo.net/

×