Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

Monad(モナド)とは何か?という問いには、様々なアプローチでの解説が試みられていますが、函数(関数)にまつわるトピックであり、抽象度が非常に高いために理解しがたいものです。

このスライドでは、MonadをC#言語を使って掘り下げることで、基本的な考え方についての足掛かりになるように構成しました。そのため、厳密な定義は含まれていないため、数学的なアプローチを参照したい場合には向きません。

このスライドは「F# 勉強会 岐阜」で発表しました。
https://connpass.com/event/52666/

Kouji Matsui

May 27, 2017
Tweet

More Decks by Kouji Matsui

Other Decks in Programming

Transcript

  1. C#でわかる
    こわくないM
    2017.5.27 GIFSHARP #1 KOUJI MATSUI (@KEKYO2)

    View full-size slide

  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

    View full-size slide

  3. はじめに
    •ずっと思考していたことを、どうやって自分
    の言葉で表現するかを考えていた。今日はそ
    のアウトプットです。
    •C#書ける人にリーチできるように考えました。
    •本日が「岐阜Sharp」であることは、もちろん
    承知しております ☺

    View full-size slide

  4. Agenda
    事の始まり
    Nullを安全になんとかしたい
    ネストしたNullの安全な処理
    名前を変える
    処理の連鎖
    LINQクエリ
    岐阜
    まとめ

    View full-size slide

  5. 事の始まり

    View full-size slide

  6. 事の始まり

    View full-size slide

  7. 事の始まり

    View full-size slide

  8. 事の始まり
    分かりやすい課題だし、
    ここからやるのが良かろう…
    ※Nullの議論や考察の深掘りは、それだけで沼であり、本題か
    ら外れるのでここでは扱いません。例えば、.NETのNullable
    はどうなのかとか。

    View full-size slide

  9. Agenda
    事の始まり
    Nullを安全になんとかしたい
    ネストしたNullの安全な処理
    名前を変える
    処理の連鎖
    LINQクエリ
    岐阜
    まとめ

    View full-size slide

  10. Nullを安全になんとかしたい
    例がアレだが…
    ジェネリックじゃない辞書

    View full-size slide

  11. Nullを安全になんとかしたい

    View full-size slide

  12. Nullを安全になんとかしたい
    .NET Frameworkの進化:
    • Dictionary : .NET 2.0で追加された。
    • bool TryGetValue(TKey key, out TValue value)
    boolの判定を強制させることで、
    失敗についてコード化させる。
    が、モヤモヤする…

    View full-size slide

  13. Nullを安全になんとかしたい
    値が存在する場合だけ、コールバック関数を実行したらどうか。
    こういうのを
    作っておき…

    View full-size slide

  14. Nullを安全になんとかしたい
    値が存在する場合だけ、コールバック関数を実行したらどうか。
    値が存在しない場合は
    単に無視され実行されない

    View full-size slide

  15. 値を安全に操作したい
    辞書の例を一般化して、
    任意の参照型インスタンスを
    安全に操作したい。
    入れ物(ValueHolder)に
    入れておき、Nullチェックする

    View full-size slide

  16. 値を安全に操作したい
    値がnullの場合は
    無視される

    View full-size slide

  17. 値を安全に操作したい
    これだと一回の操作ですべてが終わるので、ありがたみがない。
    実際には値に対して複数の処理を連鎖的に実行したいはず。そこで:
    戻り値を返す関数
    を指定できる
    関数を実行して
    戻り値を返す

    View full-size slide

  18. 値を安全に操作したい
    安全に操作できる
    何か違う…
    何でNullを手動で判定
    しているんだ

    View full-size slide

  19. TryExecuteの戻り値はT型なので、そこから先はNull検査が必要。
    では、その値をValueHolderに入れれば良いのでは?
    値を安全に操作したい
    処理が連鎖出来るが、
    いちいち入れ直す
    必要がある
    モヤる

    View full-size slide

  20. 値を安全に操作したい
    ValueHolderで
    ラップして返す
    ターミネーション
    処理用

    View full-size slide

  21. 値を安全に操作したい
    実行される
    実行されない

    View full-size slide

  22. 値を安全に操作したい
    途中からNullになると
    以降は安全に無視

    View full-size slide

  23. 値を安全に操作したい
    こんなユーティリティ
    を作っておく

    View full-size slide

  24. 値を安全に操作したい
    定義が楽になる

    View full-size slide

  25. Agenda
    事の始まり
    Nullを安全になんとかしたい
    ネストしたNullの安全な処理
    名前を変える
    処理の連鎖
    LINQクエリ
    岐阜
    まとめ

    View full-size slide

  26. ネストしたNullの安全な処理
    関数内でもValueHolderを使いたいかもしれない:
    Func

    View full-size slide

  27. ネストしたNullの安全な処理
    Funcから
    Func>に変更
    もうラップしなくても
    良くなった

    View full-size slide

  28. ネストしたNullの安全な処理
    今度はこっちがTをそのまま
    返しているのでエラー

    View full-size slide

  29. ネストしたNullの安全な処理
    Someを使ってラップして返すと
    ValueHolderとなって辻褄が合う

    View full-size slide

  30. ネストしたNullの安全な処理
    我々は、これを一般的に「Option型」呼んでいます:

    View full-size slide

  31. Agenda
    事の始まり
    Nullを安全になんとかしたい
    ネストしたNullの安全な処理
    名前を変える
    処理の連鎖
    LINQクエリ
    岐阜
    まとめ

    View full-size slide

  32. 名前を変える
    ちょっと名前に馴染みが薄
    いかもしれませんが:
    •“TryExecute”を”Bind”
    •”Some”を”Return”
    に変えます。

    View full-size slide

  33. 名前を変える
    これが、
    「Optionモナド」
    です。
    ※あるいはMaybeモナドと言う場合もあります。
    ※ツッコミが入りそうだから、ちょっとまって♡

    View full-size slide

  34. モナドを構成するもの
    モナドは、Optionだけではなく、様々な種類のものがありますが、
    ある構造を「モナド」と呼ぶには、以下の構造を持っている必要が
    あります:
    • 型構築子 : 値の型をジェネリックとして受け取ることが出来る、
    Option型そのものの事です(.NET的に言うなら、オープンジェ
    ネリック型)。
    • return関数 : Return関数の事です。Tの値をOptionに変換します
    • bind関数 : Bind関数の事です。Tの値を引数に取る関数を実行し、
    Optionの値を返します
    Wikipedia [モナド(プログラミング)]

    View full-size slide

  35. モナドを構成するもの
    名称がぶれる事がある(returnをunitと呼ぶ場合など)のは、
    処理系によって呼称がバラバラだからですが、モナドらしい構
    造を持っていれば、名称自体は重要ではありません。
    • Wikipediaでは、returnをunitとして説明しています。
    • returnはHaskellのreturnから来ています。
    • bindはflatMapと呼ぶ場合もあります。
    ただし、会話するときには相手に通じないかもしれないので
    注意したほうが良いでしょう。

    View full-size slide

  36. モナドを構成するもの
    名称以外にも、「モナドである」と言うには、以下の規則も
    備えている必要があります:
    1. return関数をそのままbind関数に適用すると、元の値のま
    まとなる:
    要するにそのまま
    ラップして返している

    View full-size slide

  37. モナドを構成するもの
    「 return関数をそのままbind関数に適用」
    を素直に書くと、より分かりやすい

    View full-size slide

  38. モナドを構成するもの
    2. ふたつの関数を続けてbindするのは、これらの関数から決
    まるひとつの関数を bind することに等しい:

    View full-size slide

  39. モナドを構成するもの
    “DEF”と”GHI”を計算する式を「先に」bindして、そ
    の式を”ABC”のOptionにbindしている:
    (A bind B) bind C ←同じ→ A bind (B bind C)

    View full-size slide

  40. モナドを構成するもの
    上記の規則のことを
    「モナド則」
    と言います。
    ※一見してモナドのような形をした型があっても、この規則に合
    致しない場合は、その型はモナドではありません。

    View full-size slide

  41. Bindを正確に実装
    ここまで述べたOption.Bindは、実は正確ではありません。
    Optionを返す関数(?)
    違う型(Option)を返し
    たくても、Tがstringなのでエラー

    View full-size slide

  42. Bindを正確に実装
    UをジェネリックとしてOptionを
    返す関数にする。
    関数がOptionを返しても正しく
    マッチする。
    • bind関数 : Bind関数の事です。Tの値を引数に取る関数を実行し、
    Optionの値を返します

    View full-size slide

  43. その他
    “TryExecute”に対応するものを調べたところ、”Match”と呼称す
    ることがありました。
    これについてはまた後で取り上げますが、とりあえず以降の
    サンプルはMatchと表記します。

    View full-size slide

  44. Agenda
    事の始まり
    Nullを安全になんとかしたい
    ネストしたNullの安全な処理
    名前を変える
    処理の連鎖
    LINQクエリ
    岐阜
    まとめ

    View full-size slide

  45. 処理の連鎖
    複雑な計算処理を安全に行おうとすると、Bindがネストするこ
    とがあたりまえになります:
    すべての値が存在する場合だけ、
    合計を計算する

    View full-size slide

  46. 処理の連鎖
    実はこの構造、驚くべきことにLINQのSelectManyにそっくりです:
    すべての値が存在する場合だけ、
    合計を計算する

    View full-size slide

  47. 処理の連鎖
    LINQで異なるところは:
    • Return(value)がnew[] { value } // 要素1個だけの配列の生成
    • Bind()がSelectMany() // ネストしたリストのアンループ
    です。
    ※最後の出力はMatchがないので、foreachで出力しています。
    ※bindがflatMapと呼ばれる事から、SelectManyに近しい感じもします。

    View full-size slide

  48. 処理の連鎖
    意味を考えてみると:
    1. 要素1個だけの配列:
    配列のインスタンス(int[])は常に存在し、要素が1個存在するか
    又は存在しないか。
    → 要素が1個存在する場合だけ処理 ≒ Nullではない場合だけ処理
    Value Null [1] [0]

    View full-size slide

  49. 処理の連鎖
    2. SelectManyで配列を返す:
    結果が1個だけ格納された、又は要素が格納されていない配列のイ
    ンスタンスを返すことで、結果がNullかどうかを間接的に表現
    3. 最終結果はIEnumerableだけど、要素が1個存在するか又は存在
    しないか、のどちらかで表現される。

    View full-size slide

  50. 処理の連鎖
    SelectManyに渡す関数のことを「継続」、このような形式を「継
    続渡し」と呼びます:
    継続となるラムダ式が3重にネストしている
    継続はコールバックとみなせる

    View full-size slide

  51. Agenda
    事の始まり
    Nullを安全になんとかしたい
    ネストしたNullの安全な処理
    名前を変える
    処理の連鎖
    LINQクエリ
    岐阜
    まとめ

    View full-size slide

  52. LINQクエリ構文
    LINQと構造がそっくりということは、OptionもわざとLINQっぽ
    く定義することで、LINQのクエリ構文に対応できます:
    Bindに対応するSelectManyの実装
    (邪魔なので)拡張メソッドで定義
    引数が多少違うのは、LINQクエリ構文
    を効率よく実行するためで、
    Bindの呼び出し回数を削減する

    View full-size slide

  53. LINQクエリ構文
    すると、このようにシンプルに書けます:
    ここらへんがなんとなくbind
    bindが全て成功(有効な値)
    すると、計算してreturn

    View full-size slide

  54. LINQクエリ構文
    このクエリ構文は、シンプルに短く書けるものの、意味が分かり
    づらいことが問題です。
    • fromがbindで、selectがreturnとは、想像しにくい。
    つまり、LINQはあくまでコレクション(シーケンス)に対する操
    作である(だからSQLっぽいキーワード)のに、その構文を全く別
    の用途に応用しているからです。
    ※私自身は、このように書けることにあまりメリットを見いだせ
    ていません…

    View full-size slide

  55. LINQクエリ構文
    ですが、なんとなく
    LINQがモナドの一種のように見える
    と言うのは分かりましたか?
    ※断定するのは去年あたりに自信がなくなって以来解決していな
    いので、やめておきます

    View full-size slide

  56. ところでこの会は
    岐阜Sharp (giFSharp)
    でしたね?

    View full-size slide

  57. Agenda
    事の始まり
    Nullを安全になんとかしたい
    ネストしたNullの安全な処理
    名前を変える
    処理の連鎖
    LINQクエリ
    岐阜
    まとめ

    View full-size slide

  58. F#でのモナド
    基本的に、C#で見せた構造と変わりません。
    (サンプルコードはC#に最適化してありますが)
    ここでは、
    「なぜC#ではなくF#を使いたいか?」
    にフォーカスします。

    View full-size slide

  59. F#でのOptionモナド
    F#でOptionモナドを使ってみます。F#標準のOption型と被るので
    ”Optional”と変更していますが、C#で書いたクラスをそのまま使えます:
    書き方もC#とほとんど同じ

    View full-size slide

  60. F#でのLINQ
    F#でのLINQも書き直して対比させてみます:
    書き方もC#とほとんど同じ
    IEnumerableの共変性を考慮した
    型推論が難しいので、ここだけ補助

    View full-size slide

  61. F#でのシーケンス
    F#ではLINQ演算子を直接使うことはあまりなく、代わりに
    シーケンス(Seq)の演算子を使います:
    SelectMany (Bind) は
    Seq.collectに対応します
    Seqなら型推論出来るので、補助は不要

    View full-size slide

  62. F#でのシーケンス
    しかし、シーケンスを使うなら、もっと良い方法があります。
    シーケンス式です:
    ここらへんがSeq.collect (bind)
    bindが全て成功(有効な値)
    すると、計算してreturn
    C#でのLINQクエリ構文と
    そっくりです

    View full-size slide

  63. F#でのシーケンス
    ここではシーケンス式自体はあまり掘り下げませんが、F#はこの
    「ブロックで囲まれた式」をビルトインで定義しています:
    •シーケンス向き(Seq) : seq { … }
    •非同期処理向き(Async) : async { … }
    •DBクエリ向き(IQueryable) : query { … }
    そして、これらビルトインワークフロー以外にも:
    •独自の計算定義 : hogehoge { … }
    → コンピュテーション式

    View full-size slide

  64. コンピュテーション式の導入
    Optionalをコンピュテーション式で使えるようにします:
    「ビルダークラス」を定義します。
    最低限、”Return”と”Bind”を定義しますが、
    既存のOptionalに転送するだけです
    このクラスのインスタンスを
    ”optional”と命名しておきます

    View full-size slide

  65. コンピュテーション式の導入
    OptionalBuilderとそのインスタンス”optional”を見えるスコープ
    に配置しておけば:
    optionalコンピュテーションブロックが
    使用可能に!!
    let! (let-bang) でbindが実行される。
    値が無効ならそれ以上評価されない
    (Optional.Bindが無視する)
    自然で穏当な式表記
    optionalで生成されたインスタンスは
    Optionalそのもの

    View full-size slide

  66. コンピュテーション式の導入
    C# LINQクエリ構文
    F# カスタムコンピュテーション式
    (&中身はOptionモナド)

    View full-size slide

  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

    View full-size slide

  68. コンピュテーション式の導入
    コンピュテーション式の欠点:
    • F#でしか使えない ☺

    View full-size slide

  69. その他
    ”Match”の由来が何かを説明するのを忘れていました。
    F#には「判別共用体」があります。これを使って強力なパター
    ンマッチングが出来るのですが:
    F#のOption型(判別共用体)
    Some: 任意の型の値を保持
    None: 値を保持しない
    パターンマッチング:
    OptionにはSomeかNoneしかありえない
    →網羅性の自動検査が出来る

    View full-size slide

  70. その他
    値が存在する・値が存在しない、の両方のパターンの処理を
    書かせるため、Matchと呼称していると思われます。
    値が存在する場合と存在しない場合の
    両方の関数を必ず書かせることで
    網羅性を担保する

    View full-size slide

  71. Agenda
    事の始まり
    Nullを安全になんとかしたい
    ネストしたNullの安全な処理
    名前を変える
    処理の連鎖
    LINQクエリ
    岐阜
    まとめ

    View full-size slide

  72. モナドとは?
    モナドとは:
    モナドは計算を表現する構造であり、計算ステップの列から
    なる。つまり、型がモナド構造をもつというのは、命令を繋げ
    るやり方、言い換えるとその型をもつ関数をネストさせる規則
    が定まっていることをいう。
    Wikipedia [モナド (プログラミング)]

    View full-size slide

  73. モナドとは?
    C#での、モナドの適用動機:
    • 正直、あまりない(だから知識共有されない?)。
    • LINQの演算子がモナドっぽいけど、普段意識することはない。
    • 文(Statement : voidな手続き)とは相性が悪い。Bindに渡す関数が
    値を返せない。
    F#での、モナドの適用動機:
    • 関数型プログラミング言語では一般的に使われる概念(?)。全て
    の式が値を返すので、自然に導入できる。
    • コンピュテーション式で自然に拡張・記述しやすく出来る

    View full-size slide

  74. モナドはデザインパターンか?
    モナドがデザインパターンと呼べるかどうかわかりませんが:
    デザインパターン(または設計パターン)とは、過去のソフ
    トウェア設計者が発見し編み出した設計ノウハウを蓄積し、名
    前をつけ、再利用しやすいように特定の規約に従ってカタログ
    化したものである。
    Wikipedia [デザインパターン]

    View full-size slide

  75. モナドはプログラミングパラダイムか?
    モナドがプログラミングパラダイムと呼べるかどうかわかり
    ませんが:
    プログラミングパラダイムは、プログラマにプログラムの見
    方を与えるものと言える。たとえば、オブジェクト指向プログ
    ラミングにおいて、プログラムとはオブジェクトをつくりそれ
    を管理するものである。関数型プログラミングにおいては、状
    態を持たない関数の評価の連続である。
    Wikipedia [プログラミングパラダイム]

    View full-size slide

  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

    View full-size slide

  77. Thanks join!
    The implementations --> GitHub: CSharpMonadic
    ◦ https://github.com/kekyo/CSharpMonadic/
    My blog
    ◦ http://www.kekyo.net/

    View full-size slide