define-compiler-macroを使ってみる
Common Lispのdefine-compiler-macro
を使ってみましょう。
ただ使うだけなら、検索すればすでに何人かの方が このマクロの正しい使い方を書いていますので、 そちらを見ていただいた方が絶対に有益な情報が得られると思います。
本投稿は、誰も話題に出していないsetf
の宣言について説明します。
まずは普通の使い方
define-compiler-macro
とは、コンパイルの時だけ展開するマクロです。
ふつうの関数だろうがマクロだろうが、
define-compiler-macro
を使えば挙動を変えられるってことになります。
ただしコンパイルの時だけですが。
「コンパイル」とは次の2つの関数で行われる動作のことです。
compile
関数compile-file
関数
それでは例文を。
(defmacro aaa () :hello) (define-compiler-macro aaa () :abc)
defmacro
のマクロaaa
は、ただ:hello
を返却するだけです。
それに対してdefine-compiler-macro
の方は、:abc
を返却しています。
実行結果を示します。
(format t "~A~%" (aaa)) -> :HELLO (format t "~A~%" (funcall (compile nil '(lambda () (aaa))))) -> :ABC
はい、何も難しくないですね。
setf
を宣言するとどうなるか?
これが厄介なんです。
何が厄介なのかは大切なので、ちゃんと説明して行きます。
まず規約ではdefine-compiler-macro
の第一引数は、function name
とされています。
function name
とは、symbol
と(setf symbol)
のことなので、
規約として次のような宣言が許されます。
(define-compiler-macro (setf name) (...) ...)
しかし、これ以上の記載が無く例文も存在しません。
つまりどう実装するかは処理系に任されることになります。
では実験してみるとどうなるかというと、
sbcl, clisp, cclでは全て同じ動作をしたため、
まずはその挙動を紹介します。
(define-compiler-macro (setf aaa) (value inst) `(setf (cdr ,inst) ,value)) (funcall (compile nil '(lambda () (let ((x (cons 10 20))) (setf (aaa x) 30) (format t "~A~%" x))))) -> (10 . 30)
例文では(setf aaa)
が(setf cdr)
と同じ挙動になるように定義しています。
こう見ると何も問題は無いように見えます。
しかしそもそもsetf
の定義は、上記のような通常のマクロでは表せられないものであり、
普通はdefine-setf-expander
によって定義するものです。
もし(setf cdr)
と同じ挙動をする通常のsetf
マクロを
作りたいのであれば次のようになります。
(define-setf-expander aaa (inst) (let ((g (gensym))) (values nil nil `(,g) `(setf (cdr ,inst) ,g) `(cdr ,inst))))
よって、次のようにdefine-compiler-macro
を
定義する処理系が存在する可能性が出てきます。
;; ★こういう処理系もあるかもね (define-compiler-macro (setf aaa) (inst) (let ((g (gensym))) (values nil nil `(,g) `(setf (cdr ,inst) ,g) `(cdr ,inst))))
存在するかどうかの確認はしていませんけどね。
個人的にはこの例の方が自然のように思えます。
では、なぜこうしなかったのか。
それは各処理系の都合なので私がわかるはずがないのですが、
もともとdefine-compiler-macro
の目的とは、
最適化や高速化のために導入されたものなのではないでしょうか。
コンパイル時にはsetf
形式を発見したら、
単純に置き換えるような仕組みであった方が何かと便利なのでしょう。
とはいっても、現状のsbcl, clisp, cclで見られる方式を採用した場合には
極めて大きな問題が浮上します。
それは、get-setf-expansion
関数が使えないということ。
もっと分かりやすく言うと、setf
マクロじゃなければ使えないということです。
例を示します。
(define-compiler-macro (setf aaa) (value inst) `(setf (cdr ,inst) ,value)) (funcall (compile nil '(lambda () (let ((x (cons 10 20))) (shiftf (aaa x) 30) (format t "~A~%" x))))) -> ERROR, ★shiftfでは使えない
面白いことに、psetf
ならうまく行きました。
(funcall (compile nil '(lambda () (let ((x (cons 10 20))) (psetf (aaa x) 30) (format t "~A~%" x))))) -> (10 . 30)
処理系を作る上では面倒この上ないです。
setf
が現れたら、コンパイルの時だけdefine-compiler-macro
の情報を
引っ張ってこなければいけないのですから。
いい加減な結論を出すとこんな感じ。
define-compiler-macro
で(setf symbol)
は定義しない方がいいかもね