FAIC モナド、パート2
2018-01-19 08:04:18
前回のFAICでは、いきなり関数型プログラマーの観点で掘り下げる代わりに、オブジェクト指向プログラマーの観点からモナドを探検し始めた。「モナド・パターン」は型に関するデザインパターンで、「モナド」とはそのパターンを利用する型だ。パターン自体を説明する前に、モナド的だけれどもすでによく馴染んでいることがほぼ間違いないと思われる型をいくつかリストアップして、それらに共通するものが何かを見ていこう1。
Nullable<T>
– nullになりえるT2を表現するFunc<T>
– 必要に応じて計算されるTを表現するLazy<T>
– 必要に応じて一度だけ計算され、キャッシュされるTを表現するTask<T>
– 非同期に計算され、今はまだ利用できないとしても将来的に利用できるようになるTを表現するIEnumerable<T>
– 順序をもっていて読み取り専用の0個以上のTのシーケンスを表現する
さて、これらの型に共通するものとは?一番分かりやすいのは、これらがちょうど1つの型引数を取るジェネリック型だということだ。さらに言えば、これらの型は あきれるほどに一般的 だ。Nullable<T>
はちょっと例外だが、これらの型は全て、Tが何であっても同じように働く。これらは元の型のセマンティクスにはまったく「依存しない」のだ。そしてNullable<T>
ですら、nullにならない値型に限定されているだけだ3。
これらのジェネリック型の別の見方は、それらが 「基礎となる」型の表現力を高める 「増幅器」 であるというものだ4。バイト型は256種類の値のうちのどれか1つである。これはとても便利であると同時にとても単純である。ジェネリック型を使えば、簡単に「nullになりえるバイトの、非同期に計算されるシーケンス」を表現することができる。これは「バイト」型に大きな力を与えるが、それでいて基本的な「バイト的な」性質を何も変更しない。
ということは、モナドとは単に1パラメータのあきれるぐらいジェネリックな型で、元となる型に概念的な「力を与える」ものというだけ?そうじゃない。「モナド・パターン」を実装するには、あと2、3個のものが必要だ。次回のFAIC。 これら5つの型に対してある演算を試し、他に共通性が見出せるかどうか見てみる。
-
これら5つの型は私がすぐ思いついたものというだけで、多分他にもあるだろう。もしよく使われているC#の型で、モナド的な性質を持っているものの例を思いついたら、コメントよろしく。 ↩︎
-
以前論じたように、値型におけるnullは、典型的には「こいつは値を持っているが、それが何かは分からない」と解釈される。たとえば、12月の純利益であるdecimal値は 確かに 存在するのだが、そのdecimal値が今は分からないので「null」としておくしかない、といったように。または、「こいつは値を持ってさえいない」と解釈されることもありえる。フランス国王の身長を今は 知らない のではなくて、フランスにはそもそも国王がいないのだから、フランス国王の身長はnullなのだ。とはいっても、どっちのセマンティクスなのかは、今回のモナド型についての議論には特に関係ないんだけど。 ↩︎
-
そしてこれが基本的には歴史の偶然だったとしても。C#が最初に実装された時に、たまたま常に
null
を許容する参照型と、null
を許容しない値型しかなく、ジェネリック型は全く存在していなかったためだ。CLRに最初からジェネリック型が存在したという空想上の世界であれば、Nullable<T>
がどんな型にも使えるように実装されていて、参照方はデフォルトでnull
を許容しないという可能性もあった。「null
になりえるstring
」を表現するのにNullable<string>
だけが許されるという型システムがあり得たかもしれない。次にあなたが新しい型システムを設計するときの参考にしてね! ↩︎ -
モナドについてのこの解釈は、かつての同僚、Wes Dyerのおかげだ。この発想を得た鍵となるステップはモナドに関する彼の記事だった。 ↩︎