define-modify-macro の謎
この文章は、 Lisp SETF Advent Calendar 2018 - Qiita の 12/7 分の記事としてかかれました。
簡単に define-modify-macro 概説
define-modify-macro
というマクロがあります。
これを使うと、「ある場所の値を読み出し、関数を適用し、結果を書き戻す」という動きをするマクロを簡単に定義できます。
以下は Hyperspecの例です:
(define-modify-macro appendf (&rest args) append "Append onto list") => APPENDF (setq x '(a b c) y x) => (A B C) (appendf x '(d e f) '(1 2 3)) => (A B C D E F 1 2 3)
ある日書いたコード
define-modify-macro
には関数名を渡すのですが、 define-modify-macro
で作ったマクロを呼ぶと、関数の第一引数にその場所の元の値が渡されます。
上に出した append
なら、そのままの順序で使えるのですが、変数の順序が合っていない関数もあったりします。
例えば、 acons
という関数があり、以下のように使います:
(defparameter *some-alist* '((:hoge . 1))) (acons :fuga 100 *some-alist*) ; => ((:FUGA . 100) (:HOGE . 1))
acons
は関数なので、当然ながら第三引数に渡したリストは変更されません。
変更するなら、 setf
などする必要があります:
(acons :fuga 100 *some-alist*) ; => ((:FUGA . 100) (:HOGE . 1)) *some-alist* ; => ((:HOGE . 1)) ; 変更されない。 (setf *some-alist* (acons :piyo 10000 *some-alist*)) *some-alist* ; => ((:PIYO . 10000) (:HOGE . 1))
上の setf
を見ていると、「define-modify-macro
にかけたい」とか思うわけですが、残念ながら acons
がリストを取るのは第三引数です。 define-modify-macro
は、第一引数に変更対象の値を渡しますから、引数の順番を並べかえたくなります。
というわけで、適当に lambda
をかましてみたくなるのです:
(define-modify-macro aconsf (key val) (lambda (ref k v) (acons k v ref)))
実際に SBCL 1.4.6 (Mac) で試してみると、うまく動いているようでした:
(defparameter *some-alist* '((:hoge . 1))) (aconsf *some-alist* :piyo 999) *some-alist* ; => ((:PIYO . 999) (:HOGE . 1))
define-modify-macro の謎
ここで define-modify-macro
の構文ですが、以下のようになっており:
define-modify-macro name lambda-list function [documentation] => name
そして、第三引数 function
は
function---a symbol.
とあるように、 symbol とされています。 つまり、上でやったようにラムダ式を与えるのは、なんと規格には書いてない挙動のようなのです。
しかし上では通ってしまっていて、 「処理系依存の現象?」と思いましたが、そうではないようで… 以下の実装は通ってしまうようです。
- SBCL 1.4.6 (Mac), SBCL 1.4.5 (Linux)
- Allegro Common Lisp 10.1 (Mac)
- Clozure CL 1.11.5 (Mac)
- CLISP 2.49.60 (Linux)
- ECL 16.1.2 (Linux)
というわけで、なぜか色々な処理系で通るんだけど典拠はない、ような挙動を発見しました、ということでした。
(12/7 12:40頃 追記)
コメントで頂きましたが、 LispWorks 7 だとエラーになるようです! やはり lambda
を渡すのは可搬性がないようです。