Clojureでアスペクト指向プログラミング(その1)

Ruby on Rails等でメタプログラミングの活用が当たり前になって久しい昨今、皆様、いかがお過ごしですか?私はClojureでマクロでメタプログラミングをエンジョイしております。いやぁ、マクロは最高っすよ。

マクロはすげぇ!

実は昔、私はマクロに否定的でした。「マクロってつまりはコードのジェネレーターでしょ?C言語の#defineでコードをジェネレートしたけど、まぁ、有意義なテクニックではあると思うれど、世界を変えるようなものではないと思うなぁ」なんて、情けないことにこんな風に考えておりました……。

とーんでもなーい。ClojureのマクロはC言語の#defineとは全く違うんです。世界が大幅私って本当に馬鹿だなぁ……。こんなに頭が悪くて、よくまぁこんなに長く生きてこれたもんだと感心しちゃう。

それはさておき、ClojureのマクロがC言語の#defineとは違う理由は、実に単純です。Clojureの言語構造が非常にシンプルだという単純な事実が、決定的な違いなのだと思います。

思い出してみてください。このブログの一番最初の投稿で、Rubyでの「1 + 1」をClojureで書くと「(+ 1 1)」になるという事を述べました。Clojureでは、(...)はデータ構造のリストを意味します。リストの要素間の区切りはスペース。つまり、(+ 1 1)は、Rubyで言うところの[:+, 1, 1]になるわけですな。このような単純なデータ構造がそのままプログラムになっちゃうというわけ。ちなみに、Clojureではリストの最初が関数名、残りは引数となります。

条件分岐のような場合も同様です。皆様に実感していただくために、以下にClojureのコードをいくつか書いてみます。

(not x)        # Javaでの「!x」
(if x y z)     # Javaでの「x ? y : z」
(doseq [x xs]  # Javaでの「for (x : xs)
  (println x)) #             System.out.println(x);」
(let [x 1]     # Javaでの「int x = 1;
  (println x)) #           System.out.println(x);」

doseqはちょっと複雑なので、簡単に解説させてください。Clojureでの[...]はベクター(可変長の配列みたいなモノ)を表します。doseqは、最初の引数のベクターのうち、最初の要素を変数名、次の要素を繰り返し対象の集合を表す変数だと考えて、集合の中身をxに一つづつxに代入して、2つ目の引数(上の例では「(println x)」)を繰り返し呼びだします(分かりづらい説明でごめんなさい。横に書いたJavaのコードを参考にしてみてください)。

letもちょっと難しいですね。letというのは一次変数の定義と値の設定みたいなモノで、上の場合だと、xに1を代入して、その上で、(println x)を呼び出すことになります。ちなみに、(let ...)の外側からは、定義されたxには触れません。

さて、このdoseqもletも、リストの中にリストが含まれています。そして、Clojureにはもちろん、リストを操作する機能があります。

ということは?プログラムの生成が、C言語の#defineのような文字列としてのコードの生成ではなくて、リストの操作でできるという事になるわけですよ。

試してみましょう。REPL(Read Eval Print Loop)と呼ばれる、Clojureの対話コンソールを起動します(Leiningenを使っている場合は、プロジェクトのフォルダでlein replで起動してみてください)。

Clojureでは、マクロはdefmacroで定義します。さっそくマクロを定義してみましょう。

user> (defmacro m [] (list println "こんにちは、世界!"))
#'user/m
user> (m)
こんにちは、世界!
nil

上のコードで使用しているlistは、リストを作る関数です。つまり、(list println "...")を実行すると(println "...")というリストが生成されるわけで、それはすなわち「こんにちは、世界!」を出力しなさいというプログラムに他なりません。ほら、プログラムでプログラムを生成することが、Clojureだと非常に簡単にできちゃったでしょ?

こんな簡単に定義できちゃうわけですから、Clojureでは、様々な機能がマクロで定義されています。例えば、andとor。一般的な言語では演算子として言語のコアで定義されるような機能であっても、マクロがあるClojureではライブラリとして定義できるのです。

もう少し想像力を働かせてみてみましょう。andとorが定義できるのですから、もっと複雑だったり、同様に基本的だったりする言語要素も定義できるはずだと思いませんか?

はい、その通り。Clojureのマクロでは、実に様々な言語要素を定義できます。実際、先程挙げた複雑な言語要素に見えるdoseqは、実はマクロです。他の例も挙げましょう。C言語でのswitchに相当する基本的な言語要素を実現しているcondも、Clojureではマクロで定義されているのです。

ということは?そう、マクロを作っていけば(実際には関数を作ることの方が多いのですけど)、新しい言語が作れちゃうんですよ。例を挙げてみます。以下のコードは、hiccupというライブラリを使用して作成した、HTML5のレイアウトを生成するコードです。

(defn layout
  [title javascript-src & article]
  (html5
   {:lang "ja"}
   [:head
    [:meta {:charset "utf-8"}]
    [:title (str "hiccup trial - " (h title))]
    (include-css "/css/hiccup-trial.css")
    (include-js (str "/js/page/" javascript-src ".js"))]
   [:body
    [:header
     [:nav
      [:ul
       [:li
        (link-to "/" "Return to Top")]
       [:li
        (link-to "/xs/search" "Search Xs")]]]]
    [:article (for [t article] t)]]))

さて、このプログラムは、HTMLを生成するための新しい言語で作られているように見えませんか?見えない場合は、頑張ってキアイで見てください。異論は一切認めません。このような特定領域に特化した言語を、DSL(Domain Specific Language)と呼びます。

DSLと呼べる程キアイが入っていない場合でも、実行時のプログラム生成というのは非常に便利です。この実行時のプログラム生成を、メタプログラミングと呼びます。

Clojureのマクロは、DSLやメタプログラミングに最適な道具なのですよ。で、DSLやメタプログラミングは、我々の人生を楽しみに溢れたものにしてくれることでしょう、……多分ね。

でも、呼び出しは面倒……

ではマクロは完璧な道具なのか?えっと、私はそうは思いません。だって、面倒くさい場合があるから。何が面倒臭いかって言うと貴方、マクロは明示的に呼び出してあげなければならないんですよ。

例を挙げます。この前、勉強を兼ねてClojureでデータ・アクセスのライブラリを作ってみたのですけど、その時に「あー面倒だなぁ」と感じたのが、コネクションを貼るためにwith-connectionマクロをわざわざ書かなければならない事でした。

具体的には、こんな感じ。

(with-connection test-db-spec
  (create-clj-dataset-objects)
  (create-table :organizations
                [:parent-organization :reference :organizations :child-organizations]
                [:name                :text]))

with-connectionマクロを呼び出すと、その内部ではRDBMSへのコネクションが使い放題になります。そのこと自体には便利なのですけれど、関数の殆どでwith-connectionとタイプしなければならないのが非常に面倒臭かったです……。

うー、どうにかしたい!

アスペクト指向プログラミング

で、このような場合に有効なのが、アスペクト指向プログラミングです。アスペクト指向プログラミングとは、クラスや関数に対して横断的に機能を差し込む技術です。Javaのフレームワークでよく使われていて、ログ出力とかトランザクション制御とか、上で挙げたRDBMSへのコネクション制御等で使ったことがある人は多いと思います。

というわけで、前置きはここまでにして本題です。Clojureでアスペクト指向プログラミングをやってみましょう……と考えたのですけれど、今日は疲れたので(実はプログラムをまだ作っていないので)ここまで。続きは次回とさせてください。

ではまたー。