みなさん、元気ですかー!?
僕は元気です。
もちろんのことですが、ここで言う「僕は元気です。」は、
「僕には持病があります。今は6週に1回通院しているのですが、現状の治療ではあまり改善が見られないので新しい治療を始めることになりました。それにあたって入院を勧められ、現在入院しています。そしてこのブログは病室のベッドで書いていますが、僕は元気です。」
の略です。皆さんお分かりですね :)
…
もう、なんかアレなんですよ。ブログとか面倒なんですよ。「どうやったら読者に伝えられるだろう」とか考えるのってすごく大変なわけですよ。僕は怠惰ですからね。
たった7文字で現状報告できればいいのに! みんなが僕の考えを読んでくれたらいいのに! F*ck!日本語をハックしたい!
…そんな怠惰な僕が使っている言語は、Lispです。
Lispは聞き上手
Lispの何がいいかって、とても聞き上手なんですよ。行間を読んでくれるってほどじゃないけど、「こういう構文が欲しいんだけど」って言ったら、ちゃんとその構文を理解してくれます。
どうやって?
それがLisp特有の「マクロ」という機能です。
マクロって何よ
Lispのコードは大きく3回にわけて実行されます。
- 読み込み時 (リードマクロ)
- コンパイル時 (マクロ)
- 実行
そのため、プログラムを実行する前にコードを書き換えることが可能なのです。これをマクロ展開と言います。マクローリンじゃないです。
マクロとは、「この構文を見たら、こういう風に書き換えて」という、僕とLispとの秘密の暗号なのです :)
かのゲーテもこう言っています。
人生で一番楽しい瞬間は、誰にも分からない二人だけの言葉で、誰にも分からない二人だけの秘密や楽しみを、ともに語り合っているときである。 —— ゲーテ
つまりはこういうことです。
プログラミングで一番楽しい瞬間は、誰にも分からない二人だけのマクロで、誰にも分からない二人だけのプログラムやエラーを、ともにREPLっているときである。 —— ゲーテ
なんと! つまりゲーテはLisperだったのだ! そしてLispが生まれる遥か昔にマクロの本質を語っていたのだよ!
な、なんだってー!(AA略
まさにその通り。Lispを知らないというのは人生の楽しさを知らない、つまりは人生を損しているのです。
さあ、今すぐプログラミングGaucheを注文する作業に戻るんだ。
高階関数じゃだめなの?
「待て待て。でも、それってほんとにマクロじゃなきゃ書けないの? 高階関数じゃだめなの?」
確かに高階関数も一種のコードジェネレータですが、マクロはもっと単純で強力なものです。
高階関数との大きな違いは、プログラムが実行される前にコードを書き換えられる、という点にあります。
つまり、それがたとえLispの構文的に間違ったコードであったとしても、実行されるコードはマクロ展開後であるため問題ないわけです。
マクロの実例
「で、そのマクロってのを使えばどういうことができるわけ?」という現実主義的な人もいるでしょう。拙いコードですが、僕が書いたマクロの実用例をお見せします。
DBコンバータ
以前PHPで書いたOpenPNE2系から3系へのDBコンバータを、勉強がてらCommon Lispで書き直してみました。
以下はそのコンバータの一部です。
(defconvert c_file->file () :use-fields (c_file_id filename original_filename r_datetime) :unique name (:id c_file_id :name (cl-ppcre:regex-replace "\\." filename "_") :original_filename original_filename :type (get-imagetype-from-name filename) :created_at r_datetime :updated_at r_datetime))
defconvert? もちろんそんな構文はLispにありません。僕が作りました。これはDBコンバータのコンバート処理を記述する構文(マクロ)です。
:use-fieldsで指定したカラムは、c_fileテーブルから取得するカラムとして使うだけでなく、実際に同名の変数に値をbindします。そのため下の処理で同名の変数を参照することも可能になっています。
:uniqueで指定したカラムは、既に同じ値のレコードがあったときにはinsertせずにwarningを表示する設定です。DBコンバータを書いていると、同じ名前のファイルとかができて「Duplicate entry..」のSQL ERRORを吐くことが多かったための設定です。
その下はそれぞれのカラムの対応です。見たらわかりますね。
これだけならまだ良い
これを動かす程度ならPerl5でも書ける”かも”しれません。高階関数を使ってカラムの対応を表現すればいけそうな気もします。変数への値のバインドが面倒かもしれませんが、黒魔術のevalとかで頑張れば、ひょっとするといけるかも?
うん。上のコードくらいならね。じゃあ、以下のコードはどうでしょう。
(defconvert c_image->file ()
:use-fields (c_image_id filename r_datetime)
:unique name
(let-variables ((max-id (get-max 'id 'file op3-db)))
(:id (+ max-id c_image_id)
:name (cl-ppcre:regex-replace "\\." filename "_")
:type (get-imagetype-from-name filename)
:created_at r_datetime
:updated_at r_datetime)))
ここではカラムの対応を、さらに let-variables という構文で囲みました。これは、このスコープ内だけで使えるローカル変数を定義するものです。上の例ではmax-idという変数を定義しています。
こんなことがPerlでできますか? …また、evalで頑張れば? だけど、そのためにはPerlコードをパースして定義部分を取り出さなければいけないでしょう。そんな苦労を僕は想像したくもありません :)
まとめ
上の例を簡潔にまとめるならば、「DBコンバータ用のプログラム言語を構築した」と言えるでしょう。defconvertなんて構文はありませんし、let-variablesという構文もありません。Lispはこういった、自分の作りたいプログラムに合った埋め込み言語を構築することができます。
そして、その埋め込み言語を使って、上のような簡潔なコンバートコードを記述することができるわけです。
もし、新しくコンバートしなきゃいけないテーブルができたら? defconvertすればいい! 一から処理を書くよりもずっと簡単に書けるはずです。
Lispはこの拡張性がゆえに、他の言語の追随を許しません。もし他の言語にあるのに、Lispにないものが使いたければ、マクロでそれを実装すればいいんです。Lispで失うものはありません。マクロ最強です。そして、そのマクロが使える言語は、Lispしかないんですよ :)




コメントする