2012-11-14-01-マクロとメタプログラミング - project-enigma

2012-11-14-01-マクロとメタプログラミング

>> Site top >> weblog >> 月別アーカイブ >> 2012年11月のlog >> 2012-11-14-01-マクロとメタプログラミング

最終更新日付:2014/01/02 00:00:00


マクロとメタプログラミング

2012 年 11 月 14 日

C++ で考えるのがデフォルトの人間は、総称とかジェネリックとかメタプログラミングというとすぐに template が頭に浮かぶ。そして、そもそも型付けの弱い言語でメタプログラミングなんてできるのだろうか、と考えるのだ。もちろんできる。C++ のようにはできないというだけのことだ。Lisper の方々が怒り出さないうちに書いておくと、「C++ のようにはできない」というのは Lisp が C++ より劣っているという意味ではない。やり方が違うというだけのことだ。そこに優劣はない(個人的な好みはあるだろうが)。

とはいえ、Common Lisp を学ぶ過程でマクロの挙動を知った時、「これは C++ よりも上だな」と素直に思った。理由は、通常実行時にコールされるような関数をコンパイル時点でもコールできてしまうことだ。最初は理解するのに時間がかかったが、Lisp ではコンパイル時と実行時の区別が曖昧だということらしい。簡単な(そしてひょっとしたら不適切かもしれない)例を以下に。

まず、階乗計算のためのありがちな関数を1つ用意。一応末尾再帰にはしてるけど、中身は(今は)問題じゃない。変な名前なのは、これを直接コールつもりはないからだ。

(defun __fact (n)
  (labels ((imp (n acc)
             (if (zerop n)
                 acc
                 (imp (1- n) (* n acc)))))
    (imp n 1)))

で、さらに以下のようなマクロを用意する(ちょっと乱暴だけど勘弁してほしい)。

(defmacro factorial (n)
  (if (integerp n)
      (__fact n)
      `(__fact ,n)))

これでなにが起きるか。普通に使うと、当たり前だけど結果は予想どおりだ。

(setq n 5)
(factorial n)       -> 120
(factorial (+ 2 3)) -> 120
(factorial 5)       -> 120

しかし、macroexpand にかけると(Lisp初心者には)驚くようなことに気付く。リテラル値を渡した場合、展開結果がすでに計算済みになっているのだ。

(macroexpand '(factorial n))       -> (__FACT N)
(macroexpand '(factorial (+ 2 3))) -> (__FACT (+ 2 3))
(macroexpand '(factorial 5))       -> 120

もちろん、これは factorial の defmacro でそう書いたからだ。Lisp のマクロに慣れた人には「何を驚いてるんだ?」と思われるかもしれないが、Lisp を良く知らない人にはとんでもないことなのだ。関数を実行時と同じようにコンパイル時にコールできるなんて!

ここに至って、Lisp でのメタプログラミングがどういうものか、ちょっと見えてきたような気がするわけだ。それはおそらくマクロを使って計算(の一部)をコンパイル時に済ませることであったり、マクロを使って構造体やクラスをコンパイル時に生成したりすることであったりするのだろう。これは On Lisp で Paul Graham が書いていたことだ。しかし、その驚きは読んだ時ではなく、飲み込めたときにやってくる(自分はそれが同時ではなかった)。

面白いじゃないか、Lisp。

 

コメント

このページにコメントする

 

このページのタグ

Page tag : Common Lisp

 

 


Copyright(C) 2005-2017 project-enigma.
Generated by CL-PREFAB.