jQueryはモナドだ
この記事はjQuery is a Monad | Important Shockという記事の勝手訳です。
追記1: bonotakeさんが補足記事を書いてくれています → JQueryがモナドかどうかとか - たけをの日記@天竺から帰ってきたよ
追記2: hirataraさんが補足記事を書いてくれています → jQueryは本当にモナドだった - 北海道苫小牧市出身のPGが書くブログ
Haskellプログラマーは誰しもがモナドに関する各々のチュートリアルを書くと言われる。というのも、一度モナドの定義とその可能性を理解すれば、モナド全体を囲む神秘性に挑戦して打ち破るのが容易になるからだ。門外漢からすれば、モナドはHaskellを真に理解することを妨げる不可解な障壁だ。モナドはとても不適当な名前で呪われていて、一風変わった文法を持ち、一度に何もかもやってしまう様に見える。しかしながら、その動きをじっくりと観察すればモナドを理解するのは難しくない。
というわけで、あらゆる言語の中でも最も広く使われているであろうモナド的なライブラリ、jQueryを紹介する。jQueryはJavaScriptを関数型プログラミングの本質へと誘い、AJAXやアニメーションを容易に扱えるように設計されている。 jQueryオブジェクトは、 jQuery変数か$というショートカットを通じてアクセスでき、下の例の様にCSSセレクタやXpathクエリを利用してDOM要素を集めたり生成したりすることができる。
$("div"); // 全てのdiv要素 $("span.moveable") // 属性にmoveableを持った全てのspan要素 $("em,strong"); // 全てのem、strong要素 $("div\[img][3]"); // img要素を持っているdiv要素の三番目多少驚くかもしれないが、jQueryはメソッドチェインのおかげでインスタンス変数の必要性を無くしている。もしjQueryなしで、全てのspan要素を取ってきて、それらをフェードインさせ、それらの要素のテキストを"Alert!"にしたいならば、おそらく経過時間の痕跡を保持し、順番にそれぞれを操作するために四つの要素を保存するためのインスタンス変数を必要とするだろう。だがjQueryではそれはとても簡単だ。
$("span").fadeIn("slow").text("Alert!");fadeInメソッドが返す値は、ややもすればnullだと思うかもしれない。がそうではなく、そのメソッド呼び出しのレシーバである $("span") というjQueryコンテナによく似たオブジェクトである。この方法では、同様に似たコンテナを返すtext("Alert!")メソッドを呼ぶことができる。このようなメソッドチェインは古くて頼りない言語に計り知れないパワーと簡潔さと読みやすさを与える。DOM操作に加えて、jQueryはAJAXのためのパワフルなショートカットや、今のところは控えめだが美しいアニメーション機能や、広範囲にわたるCSS操作を提供する。
モナド則
jQueryの持つ基本的なコンセプトを理解したところで、三つのモナド則を見て行こう。(モナドのコンセプトと三つのモナド則は、圏論として知られる数学の分科で最初に成文化された。が気をつけて、地獄のように難解だ。)
最初のモナド則は、モナドは別の型を包含するということである。Haskellでは、IO String型はそのひとつだ。ファイルや、コンソールの入力や、システムコールを読み込む関数はこの型を返す - IOはStringデータ型を包み込むモナドである。jQueryは与えられたクエリを通じて集めたDOMノードを包み込むので明白にこの条件を満たす。
二番目のモナド則は、とにかくシンプルだ。全てのモナドは他のデータ型を自身で包み込む関数を持たなければならないということである。jQueryがDOMノードに対して自身を適用する方法を持っているのは明らかだ。DOMを探索するのにクエリ機能を利用できるが、もしこれを怪しく感じるなら、jQueryオブジェクトにdocument.getElementsByTagNameの結果やその兄弟を渡して利用することもできる。Haskellではこれは型構築子を指す。型構築子はあるデータを引数に取って新しい型の中にそのデータを包み込む関数である。(jQueryの型構築子は自身の括弧だ。)
三番目のモナド則、これはほんのすこしだけ複雑だ。全てのモナドはモナドを返す関数にその値を提供出来なければならないということである。fadeIn(), text(), そして他の全てのチェイン可能な関数はこれの例となる。これらの関数はjQueryオブジェクトの内部から与えられたDOM要素を受け取ってその固有の機能を執行し、jQueryオブジェクトで再び包み込んで返す。さらにjQueryオブジェクトに組み込まれている関数だけに制限されているわけではない。map()関数を使えば、jQueryオブジェクトの内部のDOM要素ごとに呼ばれる匿名関数を渡すことができる。呼ばれたmap()はjQueryオブジェクトを返すだろう。
では再確認しよう。モナドは三つの条件を満たす抽象的なデータ型である。
- モナドは他のデータ型を包み込む
- モナドは前述したような包み込みを行う操作を持つ。紛らわしいことにこれはreturnと呼ばれる
- モナドはモナドを返すある関数に対してモナド内部の値を提供することを許可するbindと呼ばれる操作を持つ
バーン。それだけ。シンプルだ。役に立たないんじゃないかと思えるほどに。しかしモナドはオブジェクトのように概念的に非常にシンプルで、計り知れないほどにパワフルだ。Haskellでは、モナドはアクションを表す抽象的なデータ型としてしばしば使われる。というのも、モナドは一箇所でチェインされるし、伝統的な手続き型プログラミングかもしくは外界に依存したコードを書くのに完璧にマッチするからだ。Haskellでのあらゆるキーボードの入力かもしくはファイルの入力は、IOモナドに包含される。IOモナドはそのプログラムのある部分が外界に依存していることを推定しやすくしてくれる。予測出来ない入力である外界に対してプログラムのある部分が依存していると推定することで、デバッグが簡単になるだけでなく、他のそれ以外の関数が自身の入力のみに依存していることを保証する。もしHaskellが他にもどのようにモナドを利用しているかについてや、モナドのより厳密な定義について学びたい場合はJeff NewburnのAll About Monadsを参照するといい。
「こりゃすごい」あなたは言う。「だけどjQueryのモナドは普通のjQueryイディオムの中ではどのように使われてるの?」うむ、そう尋ねてくれて嬉しい。
慎重な計算
どの言語でも、nullではないオブジェクトを期待しているメソッドにnullオブジェクトを渡したり、nullオブジェクトにメッセージを送る方法をどの言語でもそれぞれに持つ。Objective-Cではnilを返し、JavaではNullPointerExceptionを投げ、C言語では、ええと、C言語ではセグメンテーションフォールトを起こすが、他に何か新しいのは? jQueryでこれに対応するのは以下のように空のjQueryオブジェクトの中身を操作しようとすることだろう。
$([]).fadeOut().text("THE WORLD HAS BROKEN!");jQueryの型構築子に空の配列を引数として渡して呼び出すことで、空のjQueryオブジェクトを得られる。 このオブジェクトでfaceOut()とtext()を呼んでも、jQueryはコンソールの至る所にエラーを吐き出したりせずに慎ましやかに失敗するだろう。nilにメッセージを送った時のObjective-Cの振る舞いかまたはRubyのandandの様に、空のjQueryオブジェクトではある点で失敗するかもしれない一連の計算を安全に集積するためにメソッドチェインを利用できる。俯瞰すると、これはHaskellのMaybeモナドにとても似ている。Maybeモナドはハッシュテーブルの値の探索のような失敗するかもしれない一連の計算を表現するのに利用される。
状態遷移
Haskellでは変数が決して変化しないと聞いたとき、私はぞっとした。 勿論、私はこのことに関していくつかやり方を知った。再帰は最初に挙がる方法だ。しかし私は破壊的に変数を更新する必要のある稀なケースがあることを知っている。だが私は悩まなくてもよい。なぜなら最も便利なモナドのひとつにStateモナドがあるからだ。Stateモナドは普通の言語で使うような変数を束縛するための媒介を提供するだけでなく、とても便利な意味論的な特徴を持つ。ある状態に依存しているということを暗に示す関数の型の中にstateモナドの真の存在感がある。(すこし考えたが、Haskellの型システムは他のどんな言語よりも表現力豊かだ)
jQueryもstateモナドのように見なすことができる。jQueryではDOMノードのセットをカプセル化し、それらに対する状態に依存した計算をチェインできる。jQueryにはクエリにマッチしたDOMを変化させるメソッドが幾つかある。add()は現在のオブジェクトに新しい要素を追加する、contents()は包含されたDOMノードの全ての子要素にマッチする、などだ。andSelf()やend()はより面白いし、Haskellのstateモナドを思い出させてくれる。これらがどのように動くのか見てみよう。
$("div.sidebar").find("a").andSelf().addClass('disabled')上のスニペットでは、$("div.sidebar")はsidebarというクラスを持ったdiv要素を見つけて、find("a")はその要素内の全てのリンクにマッチする。たった今私たちはjQueryオブジェクトを操作しているが、操作されるのはリンクの要素だけだ。そうはしないで、マッチしたdiv要素を再び加えるのにandSelf()を付け加えた。次にclass属性として"disabled"を要素に加えた。end()はandSelf()の逆の動きをする。つまり前回の破壊的な操作によって加えられた要素を取り除く。
$("div.sidebar").find("a").addClass('disabled').end().fadeOut()