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

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

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

Page 1: C#でわかる こわくないMonad

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

Page 2: C#でわかる こわくないMonad

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

Page 3: C#でわかる こわくないMonad

はじめに

•ずっと思考していたことを、どうやって自分の言葉で表現するかを考えていた。今日はそのアウトプットです。

•C#書ける人にリーチできるように考えました。

•本日が「岐阜Sharp」であることは、もちろん承知しております☺

Page 4: C#でわかる こわくないMonad

Agenda

事の始まり

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

ネストしたNullの安全な処理

名前を変える

処理の連鎖

LINQクエリ

岐阜

まとめ

Page 5: C#でわかる こわくないMonad

事の始まり

Page 6: C#でわかる こわくないMonad

事の始まり

Page 7: C#でわかる こわくないMonad

事の始まり

Page 8: C#でわかる こわくないMonad

事の始まり

分かりやすい課題だし、

ここからやるのが良かろう…

※Nullの議論や考察の深掘りは、それだけで沼であり、本題から外れるのでここでは扱いません。例えば、.NETのNullable<T>はどうなのかとか。

Page 9: C#でわかる こわくないMonad

Agenda

事の始まり

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

ネストしたNullの安全な処理

名前を変える

処理の連鎖

LINQクエリ

岐阜

まとめ

Page 10: C#でわかる こわくないMonad

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

例がアレだが…ジェネリックじゃない辞書

Page 11: C#でわかる こわくないMonad

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

Page 12: C#でわかる こわくないMonad

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

.NET Frameworkの進化:

• Dictionary<TKey, TValue> : .NET 2.0で追加された。

• bool TryGetValue(TKey key, out TValue value)

boolの判定を強制させることで、失敗についてコード化させる。

が、モヤモヤする…

Page 13: C#でわかる こわくないMonad

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

値が存在する場合だけ、コールバック関数を実行したらどうか。

こういうのを作っておき…

Page 14: C#でわかる こわくないMonad

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

値が存在する場合だけ、コールバック関数を実行したらどうか。

値が存在しない場合は単に無視され実行されない

Page 15: C#でわかる こわくないMonad

値を安全に操作したい

辞書の例を一般化して、任意の参照型インスタンスを安全に操作したい。

入れ物(ValueHolder<T>)に入れておき、Nullチェックする

Page 16: C#でわかる こわくないMonad

値を安全に操作したい

値がnullの場合は無視される

Page 17: C#でわかる こわくないMonad

値を安全に操作したい

これだと一回の操作ですべてが終わるので、ありがたみがない。

実際には値に対して複数の処理を連鎖的に実行したいはず。そこで:

戻り値を返す関数を指定できる

関数を実行して戻り値を返す

Page 18: C#でわかる こわくないMonad

値を安全に操作したい

安全に操作できる

何か違う…何でNullを手動で判定しているんだ

Page 19: C#でわかる こわくないMonad

TryExecuteの戻り値はT型なので、そこから先はNull検査が必要。

では、その値をValueHolder<T>に入れれば良いのでは?

値を安全に操作したい

処理が連鎖出来るが、いちいち入れ直す必要がある

モヤる

Page 20: C#でわかる こわくないMonad

値を安全に操作したい

ValueHolder<T>でラップして返す

ターミネーション処理用

Page 21: C#でわかる こわくないMonad

値を安全に操作したい

実行される

実行されない

Page 22: C#でわかる こわくないMonad

値を安全に操作したい

途中からNullになると

以降は安全に無視

Page 23: C#でわかる こわくないMonad

値を安全に操作したい

こんなユーティリティを作っておく

Page 24: C#でわかる こわくないMonad

値を安全に操作したい

定義が楽になる

Page 25: C#でわかる こわくないMonad

Agenda

事の始まり

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

ネストしたNullの安全な処理

名前を変える

処理の連鎖

LINQクエリ

岐阜

まとめ

Page 26: C#でわかる こわくないMonad

ネストしたNullの安全な処理

関数内でもValueHolder<T>を使いたいかもしれない:

Func<T, T>

Page 27: C#でわかる こわくないMonad

ネストしたNullの安全な処理

Func<T, T>からFunc<T, ValueHolder<T>>に変更

もうラップしなくても良くなった

Page 28: C#でわかる こわくないMonad

ネストしたNullの安全な処理

今度はこっちがTをそのまま返しているのでエラー

Page 29: C#でわかる こわくないMonad

ネストしたNullの安全な処理

Someを使ってラップして返すとValueHolder<T>となって辻褄が合う

Page 30: C#でわかる こわくないMonad

ネストしたNullの安全な処理

我々は、これを一般的に「Option型」呼んでいます:

Page 31: C#でわかる こわくないMonad

Agenda

事の始まり

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

ネストしたNullの安全な処理

名前を変える

処理の連鎖

LINQクエリ

岐阜

まとめ

Page 32: C#でわかる こわくないMonad

名前を変える

ちょっと名前に馴染みが薄いかもしれませんが:

•“TryExecute”を”Bind”

•”Some”を”Return”

に変えます。

Page 33: C#でわかる こわくないMonad

名前を変える

これが、

「Optionモナド」

です。

※あるいはMaybeモナドと言う場合もあります。

※ツッコミが入りそうだから、ちょっとまって♡

Page 34: C#でわかる こわくないMonad

モナドを構成するもの

モナドは、Optionだけではなく、様々な種類のものがありますが、ある構造を「モナド」と呼ぶには、以下の構造を持っている必要があります:

• 型構築子 : 値の型をジェネリックとして受け取ることが出来る、Option<T>型そのものの事です(.NET的に言うなら、オープンジェネリック型)。

• return関数 : Return関数の事です。Tの値をOption<T>に変換します

• bind関数 : Bind関数の事です。Tの値を引数に取る関数を実行し、Option<U>の値を返します

Wikipedia [モナド(プログラミング)]

Page 35: C#でわかる こわくないMonad

モナドを構成するもの

名称がぶれる事がある(returnをunitと呼ぶ場合など)のは、処理系によって呼称がバラバラだからですが、モナドらしい構造を持っていれば、名称自体は重要ではありません。

• Wikipediaでは、returnをunitとして説明しています。

• returnはHaskellのreturnから来ています。

• bindはflatMapと呼ぶ場合もあります。

ただし、会話するときには相手に通じないかもしれないので注意したほうが良いでしょう。

Page 36: C#でわかる こわくないMonad

モナドを構成するもの

名称以外にも、「モナドである」と言うには、以下の規則も備えている必要があります:

1. return関数をそのままbind関数に適用すると、元の値のままとなる:

要するにそのままラップして返している

Page 37: C#でわかる こわくないMonad

モナドを構成するもの

「 return関数をそのままbind関数に適用」を素直に書くと、より分かりやすい

Page 38: C#でわかる こわくないMonad

モナドを構成するもの

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

Page 39: C#でわかる こわくないMonad

モナドを構成するもの

“DEF”と”GHI”を計算する式を「先に」bindして、その式を”ABC”のOptionにbindしている:

(A bind B) bind C ←同じ→ A bind (B bind C)

Page 40: C#でわかる こわくないMonad

モナドを構成するもの

上記の規則のことを

「モナド則」

と言います。

※一見してモナドのような形をした型があっても、この規則に合致しない場合は、その型はモナドではありません。

Page 41: C#でわかる こわくないMonad

Bindを正確に実装

ここまで述べたOption<T>.Bindは、実は正確ではありません。

Option<T>を返す関数(?)

違う型(Option<CultureInfo>)を返したくても、Tがstringなのでエラー

Page 42: C#でわかる こわくないMonad

Bindを正確に実装

UをジェネリックとしてOption<U>を返す関数にする。

関数がOption<T>を返しても正しくマッチする。

• bind関数 : Bind関数の事です。Tの値を引数に取る関数を実行し、Option<U>の値を返します

Page 43: C#でわかる こわくないMonad

その他

“TryExecute”に対応するものを調べたところ、”Match”と呼称することがありました。

これについてはまた後で取り上げますが、とりあえず以降のサンプルはMatchと表記します。

Page 44: C#でわかる こわくないMonad

Agenda

事の始まり

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

ネストしたNullの安全な処理

名前を変える

処理の連鎖

LINQクエリ

岐阜

まとめ

Page 45: C#でわかる こわくないMonad

処理の連鎖

複雑な計算処理を安全に行おうとすると、Bindがネストすることがあたりまえになります:

すべての値が存在する場合だけ、合計を計算する

Page 46: C#でわかる こわくないMonad

処理の連鎖

実はこの構造、驚くべきことにLINQのSelectManyにそっくりです:

すべての値が存在する場合だけ、合計を計算する

Page 47: C#でわかる こわくないMonad

処理の連鎖

LINQで異なるところは:

• Return(value)がnew[] { value } // 要素1個だけの配列の生成

• Bind()がSelectMany() // ネストしたリストのアンループ

です。

※最後の出力はMatchがないので、foreachで出力しています。

※bindがflatMapと呼ばれる事から、SelectManyに近しい感じもします。

Page 48: C#でわかる こわくないMonad

処理の連鎖

意味を考えてみると:

1.要素1個だけの配列:配列のインスタンス(int[])は常に存在し、要素が1個存在するか又は存在しないか。→ 要素が1個存在する場合だけ処理 ≒ Nullではない場合だけ処理

Value Null [1] [0]

Page 49: C#でわかる こわくないMonad

処理の連鎖

2. SelectManyで配列を返す:結果が1個だけ格納された、又は要素が格納されていない配列のインスタンスを返すことで、結果がNullかどうかを間接的に表現

3.最終結果はIEnumerable<int>だけど、要素が1個存在するか又は存在しないか、のどちらかで表現される。

Page 50: C#でわかる こわくないMonad

処理の連鎖

SelectManyに渡す関数のことを「継続」、このような形式を「継続渡し」と呼びます:

継続となるラムダ式が3重にネストしている継続はコールバックとみなせる

Page 51: C#でわかる こわくないMonad

Agenda

事の始まり

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

ネストしたNullの安全な処理

名前を変える

処理の連鎖

LINQクエリ

岐阜

まとめ

Page 52: C#でわかる こわくないMonad

LINQクエリ構文

LINQと構造がそっくりということは、Option<T>もわざとLINQっぽく定義することで、LINQのクエリ構文に対応できます:

Bindに対応するSelectManyの実装(邪魔なので)拡張メソッドで定義

引数が多少違うのは、LINQクエリ構文を効率よく実行するためで、

Bindの呼び出し回数を削減する

Page 53: C#でわかる こわくないMonad

LINQクエリ構文

すると、このようにシンプルに書けます:

ここらへんがなんとなくbind

bindが全て成功(有効な値)すると、計算してreturn

Page 54: C#でわかる こわくないMonad

LINQクエリ構文

このクエリ構文は、シンプルに短く書けるものの、意味が分かりづらいことが問題です。

• fromがbindで、selectがreturnとは、想像しにくい。

つまり、LINQはあくまでコレクション(シーケンス)に対する操作である(だからSQLっぽいキーワード)のに、その構文を全く別の用途に応用しているからです。

※私自身は、このように書けることにあまりメリットを見いだせていません…

Page 55: C#でわかる こわくないMonad

LINQクエリ構文

ですが、なんとなく

LINQがモナドの一種のように見えると言うのは分かりましたか?

※断定するのは去年あたりに自信がなくなって以来解決していないので、やめておきます

Page 56: C#でわかる こわくないMonad

ところでこの会は

岐阜Sharp (giFSharp)

でしたね?

Page 57: C#でわかる こわくないMonad

Agenda

事の始まり

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

ネストしたNullの安全な処理

名前を変える

処理の連鎖

LINQクエリ

岐阜

まとめ

Page 58: C#でわかる こわくないMonad

F#でのモナド

基本的に、C#で見せた構造と変わりません。(サンプルコードはC#に最適化してありますが)

ここでは、

「なぜC#ではなくF#を使いたいか?」

にフォーカスします。

Page 59: C#でわかる こわくないMonad

F#でのOptionモナド

F#でOptionモナドを使ってみます。F#標準のOption型と被るので”Optional”と変更していますが、C#で書いたクラスをそのまま使えます:

書き方もC#とほとんど同じ

Page 60: C#でわかる こわくないMonad

F#でのLINQ

F#でのLINQも書き直して対比させてみます:

書き方もC#とほとんど同じ

IEnumerable<T>の共変性を考慮した型推論が難しいので、ここだけ補助

Page 61: C#でわかる こわくないMonad

F#でのシーケンス

F#ではLINQ演算子を直接使うことはあまりなく、代わりにシーケンス(Seq)の演算子を使います:

SelectMany (Bind) はSeq.collectに対応します

Seqなら型推論出来るので、補助は不要

Page 62: C#でわかる こわくないMonad

F#でのシーケンス

しかし、シーケンスを使うなら、もっと良い方法があります。シーケンス式です:

ここらへんがSeq.collect (bind)

bindが全て成功(有効な値)すると、計算してreturn

C#でのLINQクエリ構文とそっくりです

Page 63: C#でわかる こわくないMonad

F#でのシーケンス

ここではシーケンス式自体はあまり掘り下げませんが、F#はこの「ブロックで囲まれた式」をビルトインで定義しています:

•シーケンス向き(Seq<T>) : seq { … }

•非同期処理向き(Async<T>) : async { … }

•DBクエリ向き(IQueryable<T>) : query { … }

そして、これらビルトインワークフロー以外にも:

•独自の計算定義 : hogehoge { … }→ コンピュテーション式

Page 64: C#でわかる こわくないMonad

コンピュテーション式の導入

Optionalをコンピュテーション式で使えるようにします:

「ビルダークラス」を定義します。最低限、”Return”と”Bind”を定義しますが、既存のOptionalに転送するだけです

このクラスのインスタンスを”optional”と命名しておきます

Page 65: C#でわかる こわくないMonad

コンピュテーション式の導入

OptionalBuilderとそのインスタンス”optional”を見えるスコープに配置しておけば:

optionalコンピュテーションブロックが使用可能に!!

let! (let-bang) でbindが実行される。値が無効ならそれ以上評価されない(Optional.Bindが無視する)

自然で穏当な式表記

optionalで生成されたインスタンスはOptional<T>そのもの

Page 66: C#でわかる こわくないMonad

コンピュテーション式の導入

C# LINQクエリ構文

F# カスタムコンピュテーション式(&中身はOptionモナド)

Page 67: C#でわかる こわくないMonad

コンピュテーション式の導入

コンピュテーション式の利点:

• 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

Page 68: C#でわかる こわくないMonad

コンピュテーション式の導入

コンピュテーション式の欠点:

• F#でしか使えない☺

Page 69: C#でわかる こわくないMonad

その他

”Match”の由来が何かを説明するのを忘れていました。

F#には「判別共用体」があります。これを使って強力なパターンマッチングが出来るのですが:

F#のOption型(判別共用体)Some: 任意の型の値を保持

None: 値を保持しない

パターンマッチング:OptionにはSomeかNoneしかありえない

→網羅性の自動検査が出来る

Page 70: C#でわかる こわくないMonad

その他

値が存在する・値が存在しない、の両方のパターンの処理を書かせるため、Matchと呼称していると思われます。

値が存在する場合と存在しない場合の両方の関数を必ず書かせることで

網羅性を担保する

Page 71: C#でわかる こわくないMonad

Agenda

事の始まり

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

ネストしたNullの安全な処理

名前を変える

処理の連鎖

LINQクエリ

岐阜

まとめ

Page 72: C#でわかる こわくないMonad

モナドとは?

モナドとは:

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

Wikipedia [モナド (プログラミング)]

Page 73: C#でわかる こわくないMonad

モナドとは?

C#での、モナドの適用動機:

• 正直、あまりない(だから知識共有されない?)。

• LINQの演算子がモナドっぽいけど、普段意識することはない。

• 文(Statement : voidな手続き)とは相性が悪い。Bindに渡す関数が値を返せない。

F#での、モナドの適用動機:

• 関数型プログラミング言語では一般的に使われる概念(?)。全ての式が値を返すので、自然に導入できる。

• コンピュテーション式で自然に拡張・記述しやすく出来る

Page 74: C#でわかる こわくないMonad

モナドはデザインパターンか?

モナドがデザインパターンと呼べるかどうかわかりませんが:

デザインパターン(または設計パターン)とは、過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものである。

Wikipedia [デザインパターン]

Page 75: C#でわかる こわくないMonad

モナドはプログラミングパラダイムか?

モナドがプログラミングパラダイムと呼べるかどうかわかりませんが:

プログラミングパラダイムは、プログラマにプログラムの見方を与えるものと言える。たとえば、オブジェクト指向プログラミングにおいて、プログラムとはオブジェクトをつくりそれを管理するものである。関数型プログラミングにおいては、状態を持たない関数の評価の連続である。

Wikipedia [プログラミングパラダイム]

Page 76: C#でわかる こわくないMonad

謝辞

• 「モナドの脅威」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

Page 77: C#でわかる こわくないMonad

謝辞

Page 78: C#でわかる こわくないMonad

Thanks join!

The implementations --> GitHub: CSharpMonadic◦ https://github.com/kekyo/CSharpMonadic/

My blog◦ http://www.kekyo.net/