matarillo.com

The best days are ahead of us.

FAIC モナド、パート11

2018-01-19 17:21:18

原文

今日のエピソードの前にちょっとだけ公共広告を: 昨夜は「Circus Now!」ツアーのシアトル公演(しかも千秋楽)に技術スタッフとして参加した。この市民団体は、現代サーカスがアートの一形態であり、他の形式の演劇と同じように研究や補助に値すると認められる道を探しているのだ。米国にはサーカス学校がほんの一握りしかなくて学位も取れないという現状があり、一方、たとえばフランスにはサーカス学校が600ほどあるのと比べれば、まだ手付かずだが大きな可能性があることは明らかだ。主催者のDuncan Wallに会えたのは本当に感激した。彼の著書を読んでみたいと思う。


今回は SelectMany とLINQのクエリ式構文との関連性を紐解くつもりだったのだけど、その前に前回のコメント欄で指摘された点についてちょっと補足しておきたい。すでにわかっている、モナド・パターンの鍵となる点は (1)元の値をラップするモナド値を生成することができることと、(2) AM<R> に変える関数を M<A> 型の値に適用することができることだ。一つ目の操作は伝統的に unit と呼ばれていて、二つ目の操作は伝統的に bind と呼ばれているが、LINQでは SelectMany という名前だ。SelectMany と、unit を呼ぶだけのヘルパーメソッドがあれば Select を作れることも知った – そして実際は、必要なのは unitbind だけなのだから、どんなモナドでも Select を作ることができる。この操作は伝統的には fmap と呼ばれている。そして SelectMany とヘルパーメソッドを使った Where の作り方 – 実は不十分な Where だったわけだが – も説明した。ところがコメント欄で Medo さんが、一般的な モナドでは Where は作れないというもっともな指摘をくれた。というのも、私の実装は実際には3つの操作が必要だったからだ: unitbind、そして、 空シーケンスの作成 が。

この指摘は完全に正しい。シーケンスモナドの SelectSelectMany に対応するものはすべてのモナドが持っているけど、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を。


インデックスへ戻る


  1. 通常、これは算術計算とは何の関係もないことに注意。数値をラップしているモナドを考えているときには紛らわしいかもしれない。ここで説明している意味において2つのシーケンスを「加算する」というのはシーケンスを結合することで、整数値に分解したものを足し合わせることではない。 ↩︎

  2. 他のモナド則と同じように、ゼロ+m、m+ゼロ、そしてmは 論理的に 同値であることが要求されるが、 参照の等価性 は必ずしも必要ないことに注意。 ↩︎

  3. 前回のエピソードに Joker_vD さんが回答をコメントしているので、ネタバレしたくなければ読まないこと。 ↩︎