モナド: お前はもう知っている


2011年 03月 31日

はじめに

過去に私がHaskellを学び始めた時、
真っ先に疑問に思ったことはモナドの存在だった。
当時は全くと言っていいほど理解できなかったが、
最近Haskellを学び直して
ようやく理解することができた(と思う)。
という訳で、現時点での私のモナドへの理解を示すためにこの記事を書く。
ここではモナドの本質が何なのか概要を示す。
正確な説明は数多あるモナドについてのチュートリアルを参照されたい。

Hellow World問題: IO, Monad, fail

新しい言語を学ぶ時、まず間違いなくHello Worldを書くだろう。
HaskellでHello Worldを書くとこうなる:

main = putStrLn "Hello World"

この1行だけを見ると普通の命令型言語と大して変わらないように思える。

ところでHaskellには強力な型推論がある。
そのため型宣言を省略しても処理系がよしなに解釈してくれる。
ただ普通はコードの意図するところを明確にするために型宣言は書く
(むしろ型のレベルで物事を考えてから詳細を書き始める方がHaskell的らしい)。

さて、敢えてmainの型を書くとこうなる:

main :: IO ()

これは

  • main が入出力を伴う関数(だと今は思っておいてほしい)
  • 引数は0個
  • 戻り値の型は「値がない」

ということを表す。

Haskellで「値がない」ことを表す型は () だ。
なら main の型は () で良さそうなものの、実際には IO () である。
この IO は関数に対するタグのようなもので、
IO タグが付いている関数は入出力を伴うものとして処理系に認知される。
何故 IO タグが必要かというと、
Haskellの世界では副作用は存在しないことになっているからだ。
副作用なしという幻想を破ることなく入出力という副作用を取り扱うには
色々と対策が必要になる。
そのため、対策が必要な関数には IO タグを付けることになっている。

例えばCでHello Worldを書く場合、
コードの最初に #include を書くことになる。
Cを学び始めるときはこの #include を「おまじない」だと教わるだろう。
この「おまじない」は色々な理由があって書かなければならないのだけど、
Hello Worldを写経する段階の人には説明しても仕方がないことなので、
「おまじない」として説明は後回しにされる。

Haskellの IO タグはCの #include 同様の「おまじない」だ。
最初は「おまじない」の正体は知らなくていいし分からなくていい。
でも、そうは言われても「おまじない」の正体を知りたくなるのが人間の性だ。
そして数多のHaskellチュートリアルには IO の導入に際して「HaskellのI/Oシステムはちょっとひるんでしまうような数学の基盤、モナドを基礎として築かれています」「しかし、I/O システムを使う上で底流にあるモナドの理論を理解する必要はありません」のようなことが記載されているものだ。
これは丁寧な注意書きだ。
実際、私がHaskellを知らない人にHaskellでの入出力の仕方を説明するとしたら、
同じような注意書きをするだろう。
しかし捉え方によってはこの注意書きからかなり挑発的な匂いがする。
これはもうモナドについて調べずにはいられない。

幸いモナドについても数多のチュートリアルがある。
そこで適当なモナドの説明を読んでみるが、
良くも悪くもさっぱり理解できない。
モナド則とかいうルールがあるらしく、
中核となるのは >>= という演算子らしく、
>>= の実装はものによって色々ということは分かったけれど、
一体何が面白いのか、
そもそもモナドとやらがどう役に立つのかさっぱり理解できない。
心が折れそうである。

ところが、実のところモナドは身近なところにある。
今時の人なら十中八九使ったことがある。
ひょっとしたら一度や二度は自分で実装もしているだろう。
単にそれがモナドだと気付いていないだけだ。

実例: jQuery

今時の人なら十中八九
jQuery
を使ったことがあるはずだ。
簡潔かつ強力でとても使い易いこのJavaScriptライブラリだが、
実はjQueryのAPIはモナドを利用して構築されている。

知らない人向けには紹介がてら、
知ってる人には復習がてら、
jQueryの利用例をいくつか挙げよう:

DOMへのアクセス

jQueryでは
CSSのセレクター構文
と同様の形式でDOMへのアクセスができる。
例えば以下のような感じだ:

$('#id')
$('.parent > .child')
$('li:odd')

いちいち getElementById のような長ったらしい名前を書かなくても良いし、
複数の要素をかき集めることが簡単にできるし、
さらに「奇数番目の要素」といったよくあるパターンについては
専用の拡張構文が用意されている。

これだけでも便利なのだが、
jQueryの真価はこんなものではない。

フィルタリング

DOMのアクセスにしてもセレクターでは表現が困難なものはいくらでもあるし、
選択対象が動的に切り替わるようなものだとセレクターでは辛い。
という訳で一度取得した要素に対して追加でフィルタリングしたり
DOM構造をトラバースすることが簡単にできるようになっている。
例えば以下のような感じだ:

$('.selected')
  .closest('tr')
  .prevAll()

これは

  1. class="selected" な要素を基点にして
  2. それを子孫要素として含む最も近い tr 要素を探しだし
  3. その要素より先に出現する兄弟要素を全て取得する

という意味になる。

1つのフィルタリングやトラバースが1メソッドになっており、
メソッドチェーンを利用して簡潔に記述でき、
なおかつ意図するところが簡単に理解できるコードになっている。
また、この例では追加のフィルタリングは2回だが、
同じ形式でいくらでも追加することができる。

DOMの操作

jQueryではDOMの操作も簡単に行なえるようになっている。
形式としては先程のフィルタリングと全く同じで、
例えば以下のような感じだ:

$('#id')
  .addClass('hi');

$('li:odd')
  .addClass('hey')
  .addClass('ho');

$('#no-such-element')
  .addClass('yo');

addClass は該当要素の class 属性に指定した値を追加するメソッドだ。

#id のように該当要素が1個でも、
li:odd のように該当要素が複数個でも、
さらには #no-such-element のように該当要素が見つからなくても、
全く同じ記述でDOMの操作が行なえる。
該当要素の個数で場合分けをする必要がないので、
非常に便利だ。

アニメーション

例えばユーザーの操作に応じてメニュー項目やプレビューを表示する場合、
いきなり表示すると何が起こったのか分かりづらいので、
フェードインやスライドで徐々に表示させたいことはよくある。
一応、アニメーション効果は
setTimeout を利用して一定間隔でCSSの各種プロパティを調整してやれば
実現できるが、さすがにこれを一から書く気にはなれない。

という訳でjQueryではそういったアニメーションを
簡単に記述できるようになっている。
例えば以下のような感じだ:

$('#foo')
  .fadeOut(1000)
  .delay(500)
  .fadeIn(1000);

これは

  1. #foo を1000ミリ秒かけてフェードアウトし、
  2. 500ミリ秒の間を空け、
  3. #foo 1000ミリ秒かけてフェードインする

ことを意味する。

1つのアニメーション指示が1つのメソッドになっている。
その他の特徴はフィルタリングの例と同様だ。
もう少し言えば、
フィルタリングの場合は各メソッドに対応する処理が逐次実行されるだけだったが、
アニメーションの場合は各メソッドに対応する効果が内部のキューに追加され、
アニメーション効果そのものは非同期に実行されている。

結論: モナドはどこにあるの? 結局モナドって何?

結論から言えば、モナドとは本当にやりたいことのみを書き並べ、 各々のやりたいことの間を繋ぐ裏方作業をコード上から消し去る設計だ。
もっと端的に言ってしまえば、
プログラミングにおいてモナドはデザインパターンの一種に過ぎない。
内容が抽象的で応用範囲が広いので、
今までモナドを使っていてもそれがモナドというパターンだと認識できていなかっただけだ。

先程のjQueryの利用例だが、全てモナドが潜んでいる:

  • 「フィルタリング」の例では、
    「本当にやりたいこと」はDOMのフィルタリングやトラバースで、
    処理対象の要素をどのように受け取るかや
    処理結果をどのようにかき集めるかといった「裏方作業」は
    メソッドチェーンの . の中に隠れている。
  • 「DOMの操作」の例では、
    「本当にやりたいこと」は属性の変更や要素の追加などのDOMの操作で、
    処理対象の要素がいくつあるのかや
    後続の処理のために適切な要素を返すかといった「裏方作業」は
    メソッドチェーンの . の中に隠れている。
  • 「アニメーション」の例では、
    「本当にやりたいこと」はアニメーション効果であって、
    アニメーション効果のキュー管理や
    各効果の実行といった「裏方作業」は
    メソッドチェーンの . の中に隠れている。

このように、
jQueryのAPIは単に利用しているだけだと特に共通項はないように思えるが、
実際にはモナドという共通のパターンが潜んでいる。

また、Haskellでは空気のようにモナドを使うが、
これは言語として陽にモナドを取り扱うことができることと、
標準ライブラリの多くがモナドを利用して構築されていることに由来する。
別にHaskellが特別という訳ではなく、
モナドが汎用的な設計のパターンで便利なAPIを構築することができるからだ。
jQueryのようにHaskell以外の言語でもモナドを利用した設計は可能だ。

という訳で、モナドは簡単だ。
今までのプログラミングの経験の中で十中八九使ったことがある。
ひょっとしたら一度や二度は自分で実装もしているだろう。
単にそれがモナドだと気付いていなかっただけだ。

補足: モナドの構成要素とモナド則

実は上の説明では1つ抜けているところがある。
「先程のjQueryの利用例だが、全てモナドが潜んでいる」
と書いておきながら
「DOMへのアクセス」
のどこにモナドが潜んでいるか説明していない。
これは「DOMへのアクセス」と他の利用例で
潜んでいるモナドの姿に違いがあるからだ。

前述通り、
モナドの本質は「本当にやりたいこと」の連鎖と「裏方作業」の隠蔽だ。
ただ「本当にやりたいこと」の連鎖を繋げていくためには
始点を決めなければならない。
「DOMへのアクセス」では連鎖の始点を決めるという形でモナドが潜んでいる
(jQueryでは $ 、Haskell語では return)。
一方、他の利用例では連鎖を繋げるという形でモナドが潜んでいる
(jQueryでは . 、Haskell語では >>=)。

モナドの構成要素としては return>>= 以外にも説明していないものがある。
また、これらの構成要素の間にはモナド則と呼ばれるルールがあるが、
これも説明していない。
端的に言えばこれは「本当にやりたいこと」の連鎖を繋げていったときに、
全体が期待通りに振る舞うことを保証するためのルールに過ぎない。
正確な解説は
他のモナドに関するチュートリアル
を参照されたい。
ここまで読めた人ならどのチュートリアルもすらすら読めるはずだ。

あとがき

ここまで書いた時点で、ふと思って調べてみたら、案の定
同じようなことを考えている人は既にいた