FAIC モナド、パート11
2018-01-19 17:21:18
今日のエピソードの前にちょっとだけ公共広告を: 昨夜は「Circus Now!」ツアーのシアトル公演(しかも千秋楽)に技術スタッフとして参加した。この市民団体は、現代サーカスがアートの一形態であり、他の形式の演劇と同じように研究や補助に値すると認められる道を探しているのだ。米国にはサーカス学校がほんの一握りしかなくて学位も取れないという現状があり、一方、たとえばフランスにはサーカス学校が600ほどあるのと比べれば、まだ手付かずだが大きな可能性があることは明らかだ。主催者のDuncan Wallに会えたのは本当に感激した。彼の著書を読んでみたいと思う。
今回は SelectMany
とLINQのクエリ式構文との関連性を紐解くつもりだったのだけど、その前に前回のコメント欄で指摘された点についてちょっと補足しておきたい。すでにわかっている、モナド・パターンの鍵となる点は (1)元の値をラップするモナド値を生成することができることと、(2) A
を M<R>
に変える関数を M<A>
型の値に適用することができることだ。一つ目の操作は伝統的に unit
と呼ばれていて、二つ目の操作は伝統的に bind
と呼ばれているが、LINQでは SelectMany
という名前だ。SelectMany
と、unit
を呼ぶだけのヘルパーメソッドがあれば Select
を作れることも知った – そして実際は、必要なのは unit
と bind
だけなのだから、どんなモナドでも Select
を作ることができる。この操作は伝統的には fmap
と呼ばれている。そして SelectMany
とヘルパーメソッドを使った Where
の作り方 – 実は不十分な Where
だったわけだが – も説明した。ところがコメント欄で Medo さんが、一般的な モナドでは Where
は作れないというもっともな指摘をくれた。というのも、私の実装は実際には3つの操作が必要だったからだ: unit
、bind
、そして、 空シーケンスの作成 が。
この指摘は完全に正しい。シーケンスモナドの Select
と SelectMany
に対応するものはすべてのモナドが持っているけど、Where
に対応するものはすべてのモナドが持っているわけではない。
Where
パターンを一般化できるか試してみよう。前回の WhereHelper
メソッドの動きを理解するときにちょっと注意が必要なのは、条件付きで yield return
を使っているからだ。ヘルパーの基本的な動作は次のとおりだ: 述語関数がtrueのときは unit
処理を行い、要素数1のシーケンスを返す。述語関数がfalseのときは要素数ゼロのシーケンスを返す。要素数ゼロのシーケンスの重要な事実は、どんなシーケンスと結合しても元のシーケンスを変更しないということだ。
数学において、ゼロという数には2つの実に面白い特徴がある。一つ目は、ゼロは 加法単位元 だということだ。任意の数に0を加えても、結果は元の数と同じだ。二つ目は、ゼロに何をかけてもゼロになるということだ。「シーケンスの加算」が 同じ型のシーケンス2つの結合 と定義されているのであれば、空シーケンスが加法単位元になっていることはまちがいなさそうだ。
空のシーケンスは 乗法 の意味でもゼロだろうか?っていうか、この疑問には意味があるのか?ある。2つのシーケンスの積を取る方法はわかるよね:
from x in sequence1
from y in sequence2
select new {x, y}
ご存知のように、これは2つのシーケンスの デカルト積 だ – これは 積 のひとつで、乗法 とみなせる。これは偶然の一致ではないよ。じゃあこれは:
from x in AnEmptySequence
from y in AnyFunction(x)
select y
どうなるか?明らかに、AnyFunction(x)
がどんなシーケンスを返そうが結果は空のシーケンスにならないといけない。そしたらこれはどうだ:
from x in AnySequence
from y in AnEmptySequence
select y
こっちも同じように、AnySequence
が何を返そうが空のシーケンスにならないといけない。乗法が SelectMany
で定義されるシーケンスモナドにおいては、空のシーケンスは乗法のゼロだ!
今回見出したのはモナド・パターンの副パターンだ。「加法的モナド」は、モナド・パターンの既存のルールに以下の5つのルールを追加したモナドのことだ:
- モナド型の生成の中にはゼロ値が存在する。
- 元の型が同じ2つのモナドを加算する方法がある。1
- ゼロ値は加法の単位元である。2
- ゼロにどんな関数をバインドしても結果はゼロである。(ゼロに何をかけてもゼロである。)
- 任意の
M<A>
にa=>zero
という関数をバインドした結果はゼロである。(何にゼロをかけてもゼロである。)
これで任意の加法的モナドに Where
に対応するものを定義できる。述語関数がfalseとなる場合にヘルパーメソッドがゼロを返せばいい。
ここでひとつ練習問題を: この連載記事の中で見てきた5つのモナド型 – OnDemand<T>
、Task<T>
、Nullable<T>
、Lazy<T>
、IEnumerable<T>
の中で、加法的モナドはどれでしょう?そしてそれらについて、何がゼロ値となり、何がモナドの加法演算となるでしょうか?3
次回のFAIC: 当初の予定通り、LINQのSelectManyを。