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
PRO

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 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 Slide

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

    View Slide

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

    View Slide

  5. 事の始まり

    View Slide

  6. 事の始まり

    View Slide

  7. 事の始まり

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View 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 Slide

  77. 謝辞

    View Slide

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

    View Slide