協同クリエーション サッポロビールのわくわくブルワリー ......課題 • サッポロビールは、ウェブで自由に カスタマイズできるオリジナルラベ
C#でわかる こわくないMonad
-
Upload
kouji-matsui -
Category
Software
-
view
8.594 -
download
0
Transcript of C#でわかる こわくないMonad
C#でわかるこわくないM2017.5.27 GIFSHARP #1 KOUJI MATSUI (@KEKYO2)
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
はじめに
•ずっと思考していたことを、どうやって自分の言葉で表現するかを考えていた。今日はそのアウトプットです。
•C#書ける人にリーチできるように考えました。
•本日が「岐阜Sharp」であることは、もちろん承知しております☺
Agenda
事の始まり
Nullを安全になんとかしたい
ネストしたNullの安全な処理
名前を変える
処理の連鎖
LINQクエリ
岐阜
まとめ
事の始まり
事の始まり
事の始まり
事の始まり
分かりやすい課題だし、
ここからやるのが良かろう…
※Nullの議論や考察の深掘りは、それだけで沼であり、本題から外れるのでここでは扱いません。例えば、.NETのNullable<T>はどうなのかとか。
Agenda
事の始まり
Nullを安全になんとかしたい
ネストしたNullの安全な処理
名前を変える
処理の連鎖
LINQクエリ
岐阜
まとめ
Nullを安全になんとかしたい
例がアレだが…ジェネリックじゃない辞書
Nullを安全になんとかしたい
Nullを安全になんとかしたい
.NET Frameworkの進化:
• Dictionary<TKey, TValue> : .NET 2.0で追加された。
• bool TryGetValue(TKey key, out TValue value)
boolの判定を強制させることで、失敗についてコード化させる。
が、モヤモヤする…
Nullを安全になんとかしたい
値が存在する場合だけ、コールバック関数を実行したらどうか。
こういうのを作っておき…
Nullを安全になんとかしたい
値が存在する場合だけ、コールバック関数を実行したらどうか。
値が存在しない場合は単に無視され実行されない
値を安全に操作したい
辞書の例を一般化して、任意の参照型インスタンスを安全に操作したい。
入れ物(ValueHolder<T>)に入れておき、Nullチェックする
値を安全に操作したい
値がnullの場合は無視される
値を安全に操作したい
これだと一回の操作ですべてが終わるので、ありがたみがない。
実際には値に対して複数の処理を連鎖的に実行したいはず。そこで:
戻り値を返す関数を指定できる
関数を実行して戻り値を返す
値を安全に操作したい
安全に操作できる
何か違う…何でNullを手動で判定しているんだ
TryExecuteの戻り値はT型なので、そこから先はNull検査が必要。
では、その値をValueHolder<T>に入れれば良いのでは?
値を安全に操作したい
処理が連鎖出来るが、いちいち入れ直す必要がある
モヤる
値を安全に操作したい
ValueHolder<T>でラップして返す
ターミネーション処理用
値を安全に操作したい
実行される
実行されない
値を安全に操作したい
途中からNullになると
以降は安全に無視
値を安全に操作したい
こんなユーティリティを作っておく
値を安全に操作したい
定義が楽になる
Agenda
事の始まり
Nullを安全になんとかしたい
ネストしたNullの安全な処理
名前を変える
処理の連鎖
LINQクエリ
岐阜
まとめ
ネストしたNullの安全な処理
関数内でもValueHolder<T>を使いたいかもしれない:
Func<T, T>
ネストしたNullの安全な処理
Func<T, T>からFunc<T, ValueHolder<T>>に変更
もうラップしなくても良くなった
ネストしたNullの安全な処理
今度はこっちがTをそのまま返しているのでエラー
ネストしたNullの安全な処理
Someを使ってラップして返すとValueHolder<T>となって辻褄が合う
ネストしたNullの安全な処理
我々は、これを一般的に「Option型」呼んでいます:
Agenda
事の始まり
Nullを安全になんとかしたい
ネストしたNullの安全な処理
名前を変える
処理の連鎖
LINQクエリ
岐阜
まとめ
名前を変える
ちょっと名前に馴染みが薄いかもしれませんが:
•“TryExecute”を”Bind”
•”Some”を”Return”
に変えます。
名前を変える
これが、
「Optionモナド」
です。
※あるいはMaybeモナドと言う場合もあります。
※ツッコミが入りそうだから、ちょっとまって♡
モナドを構成するもの
モナドは、Optionだけではなく、様々な種類のものがありますが、ある構造を「モナド」と呼ぶには、以下の構造を持っている必要があります:
• 型構築子 : 値の型をジェネリックとして受け取ることが出来る、Option<T>型そのものの事です(.NET的に言うなら、オープンジェネリック型)。
• return関数 : Return関数の事です。Tの値をOption<T>に変換します
• bind関数 : Bind関数の事です。Tの値を引数に取る関数を実行し、Option<U>の値を返します
Wikipedia [モナド(プログラミング)]
モナドを構成するもの
名称がぶれる事がある(returnをunitと呼ぶ場合など)のは、処理系によって呼称がバラバラだからですが、モナドらしい構造を持っていれば、名称自体は重要ではありません。
• Wikipediaでは、returnをunitとして説明しています。
• returnはHaskellのreturnから来ています。
• bindはflatMapと呼ぶ場合もあります。
ただし、会話するときには相手に通じないかもしれないので注意したほうが良いでしょう。
モナドを構成するもの
名称以外にも、「モナドである」と言うには、以下の規則も備えている必要があります:
1. return関数をそのままbind関数に適用すると、元の値のままとなる:
要するにそのままラップして返している
モナドを構成するもの
「 return関数をそのままbind関数に適用」を素直に書くと、より分かりやすい
モナドを構成するもの
2. ふたつの関数を続けてbindするのは、これらの関数から決まるひとつの関数を bind することに等しい:
モナドを構成するもの
“DEF”と”GHI”を計算する式を「先に」bindして、その式を”ABC”のOptionにbindしている:
(A bind B) bind C ←同じ→ A bind (B bind C)
モナドを構成するもの
上記の規則のことを
「モナド則」
と言います。
※一見してモナドのような形をした型があっても、この規則に合致しない場合は、その型はモナドではありません。
Bindを正確に実装
ここまで述べたOption<T>.Bindは、実は正確ではありません。
Option<T>を返す関数(?)
違う型(Option<CultureInfo>)を返したくても、Tがstringなのでエラー
Bindを正確に実装
UをジェネリックとしてOption<U>を返す関数にする。
関数がOption<T>を返しても正しくマッチする。
• bind関数 : Bind関数の事です。Tの値を引数に取る関数を実行し、Option<U>の値を返します
その他
“TryExecute”に対応するものを調べたところ、”Match”と呼称することがありました。
これについてはまた後で取り上げますが、とりあえず以降のサンプルはMatchと表記します。
Agenda
事の始まり
Nullを安全になんとかしたい
ネストしたNullの安全な処理
名前を変える
処理の連鎖
LINQクエリ
岐阜
まとめ
処理の連鎖
複雑な計算処理を安全に行おうとすると、Bindがネストすることがあたりまえになります:
すべての値が存在する場合だけ、合計を計算する
処理の連鎖
実はこの構造、驚くべきことにLINQのSelectManyにそっくりです:
すべての値が存在する場合だけ、合計を計算する
処理の連鎖
LINQで異なるところは:
• Return(value)がnew[] { value } // 要素1個だけの配列の生成
• Bind()がSelectMany() // ネストしたリストのアンループ
です。
※最後の出力はMatchがないので、foreachで出力しています。
※bindがflatMapと呼ばれる事から、SelectManyに近しい感じもします。
処理の連鎖
意味を考えてみると:
1.要素1個だけの配列:配列のインスタンス(int[])は常に存在し、要素が1個存在するか又は存在しないか。→ 要素が1個存在する場合だけ処理 ≒ Nullではない場合だけ処理
Value Null [1] [0]
処理の連鎖
2. SelectManyで配列を返す:結果が1個だけ格納された、又は要素が格納されていない配列のインスタンスを返すことで、結果がNullかどうかを間接的に表現
3.最終結果はIEnumerable<int>だけど、要素が1個存在するか又は存在しないか、のどちらかで表現される。
処理の連鎖
SelectManyに渡す関数のことを「継続」、このような形式を「継続渡し」と呼びます:
継続となるラムダ式が3重にネストしている継続はコールバックとみなせる
Agenda
事の始まり
Nullを安全になんとかしたい
ネストしたNullの安全な処理
名前を変える
処理の連鎖
LINQクエリ
岐阜
まとめ
LINQクエリ構文
LINQと構造がそっくりということは、Option<T>もわざとLINQっぽく定義することで、LINQのクエリ構文に対応できます:
Bindに対応するSelectManyの実装(邪魔なので)拡張メソッドで定義
引数が多少違うのは、LINQクエリ構文を効率よく実行するためで、
Bindの呼び出し回数を削減する
LINQクエリ構文
すると、このようにシンプルに書けます:
ここらへんがなんとなくbind
bindが全て成功(有効な値)すると、計算してreturn
LINQクエリ構文
このクエリ構文は、シンプルに短く書けるものの、意味が分かりづらいことが問題です。
• fromがbindで、selectがreturnとは、想像しにくい。
つまり、LINQはあくまでコレクション(シーケンス)に対する操作である(だからSQLっぽいキーワード)のに、その構文を全く別の用途に応用しているからです。
※私自身は、このように書けることにあまりメリットを見いだせていません…
LINQクエリ構文
ですが、なんとなく
LINQがモナドの一種のように見えると言うのは分かりましたか?
※断定するのは去年あたりに自信がなくなって以来解決していないので、やめておきます
ところでこの会は
岐阜Sharp (giFSharp)
でしたね?
Agenda
事の始まり
Nullを安全になんとかしたい
ネストしたNullの安全な処理
名前を変える
処理の連鎖
LINQクエリ
岐阜
まとめ
F#でのモナド
基本的に、C#で見せた構造と変わりません。(サンプルコードはC#に最適化してありますが)
ここでは、
「なぜC#ではなくF#を使いたいか?」
にフォーカスします。
F#でのOptionモナド
F#でOptionモナドを使ってみます。F#標準のOption型と被るので”Optional”と変更していますが、C#で書いたクラスをそのまま使えます:
書き方もC#とほとんど同じ
F#でのLINQ
F#でのLINQも書き直して対比させてみます:
書き方もC#とほとんど同じ
IEnumerable<T>の共変性を考慮した型推論が難しいので、ここだけ補助
F#でのシーケンス
F#ではLINQ演算子を直接使うことはあまりなく、代わりにシーケンス(Seq)の演算子を使います:
SelectMany (Bind) はSeq.collectに対応します
Seqなら型推論出来るので、補助は不要
F#でのシーケンス
しかし、シーケンスを使うなら、もっと良い方法があります。シーケンス式です:
ここらへんがSeq.collect (bind)
bindが全て成功(有効な値)すると、計算してreturn
C#でのLINQクエリ構文とそっくりです
F#でのシーケンス
ここではシーケンス式自体はあまり掘り下げませんが、F#はこの「ブロックで囲まれた式」をビルトインで定義しています:
•シーケンス向き(Seq<T>) : seq { … }
•非同期処理向き(Async<T>) : async { … }
•DBクエリ向き(IQueryable<T>) : query { … }
そして、これらビルトインワークフロー以外にも:
•独自の計算定義 : hogehoge { … }→ コンピュテーション式
コンピュテーション式の導入
Optionalをコンピュテーション式で使えるようにします:
「ビルダークラス」を定義します。最低限、”Return”と”Bind”を定義しますが、既存のOptionalに転送するだけです
このクラスのインスタンスを”optional”と命名しておきます
コンピュテーション式の導入
OptionalBuilderとそのインスタンス”optional”を見えるスコープに配置しておけば:
optionalコンピュテーションブロックが使用可能に!!
let! (let-bang) でbindが実行される。値が無効ならそれ以上評価されない(Optional.Bindが無視する)
自然で穏当な式表記
optionalで生成されたインスタンスはOptional<T>そのもの
コンピュテーション式の導入
C# LINQクエリ構文
F# カスタムコンピュテーション式(&中身はOptionモナド)
コンピュテーション式の導入
コンピュテーション式の利点:
• 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
コンピュテーション式の導入
コンピュテーション式の欠点:
• F#でしか使えない☺
その他
”Match”の由来が何かを説明するのを忘れていました。
F#には「判別共用体」があります。これを使って強力なパターンマッチングが出来るのですが:
F#のOption型(判別共用体)Some: 任意の型の値を保持
None: 値を保持しない
パターンマッチング:OptionにはSomeかNoneしかありえない
→網羅性の自動検査が出来る
その他
値が存在する・値が存在しない、の両方のパターンの処理を書かせるため、Matchと呼称していると思われます。
値が存在する場合と存在しない場合の両方の関数を必ず書かせることで
網羅性を担保する
Agenda
事の始まり
Nullを安全になんとかしたい
ネストしたNullの安全な処理
名前を変える
処理の連鎖
LINQクエリ
岐阜
まとめ
モナドとは?
モナドとは:
モナドは計算を表現する構造であり、計算ステップの列からなる。つまり、型がモナド構造をもつというのは、命令を繋げるやり方、言い換えるとその型をもつ関数をネストさせる規則が定まっていることをいう。
Wikipedia [モナド (プログラミング)]
モナドとは?
C#での、モナドの適用動機:
• 正直、あまりない(だから知識共有されない?)。
• LINQの演算子がモナドっぽいけど、普段意識することはない。
• 文(Statement : voidな手続き)とは相性が悪い。Bindに渡す関数が値を返せない。
F#での、モナドの適用動機:
• 関数型プログラミング言語では一般的に使われる概念(?)。全ての式が値を返すので、自然に導入できる。
• コンピュテーション式で自然に拡張・記述しやすく出来る
モナドはデザインパターンか?
モナドがデザインパターンと呼べるかどうかわかりませんが:
デザインパターン(または設計パターン)とは、過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものである。
Wikipedia [デザインパターン]
モナドはプログラミングパラダイムか?
モナドがプログラミングパラダイムと呼べるかどうかわかりませんが:
プログラミングパラダイムは、プログラマにプログラムの見方を与えるものと言える。たとえば、オブジェクト指向プログラミングにおいて、プログラムとはオブジェクトをつくりそれを管理するものである。関数型プログラミングにおいては、状態を持たない関数の評価の連続である。
Wikipedia [プログラミングパラダイム]
謝辞
• 「モナドの脅威」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
謝辞
Thanks join!
The implementations --> GitHub: CSharpMonadic◦ https://github.com/kekyo/CSharpMonadic/
My blog◦ http://www.kekyo.net/