アナフォリックマクロとpacakge(2)
半年くらい前に書いた「アナフォリックマクロとpacakge」の続き。この方法だとダメなケースを発見したので、その改善策(?)を考えてみる。
以前の方法
以前の方法は、アナフォリックマクロの定義と使用が別パッケージに分かれる場合に、アナフォリック変数のインターンを使用側パッケージで行う、というようなものだった。
(defpackage :util (:use :common-lisp) (:export a.when)) (in-package :util) (defmacro a.when (expr &body body) `(let ((,(intern "IT") ,expr)) ; ITシンボルは、マクロ展開時に使用側パッケージにインターンされる (when ,(intern "IT") ,@body)))
この方法の利点は前回書いた通りなのだが、a.whenマクロを使用する関数がインライン宣言されており*1、その関数がさらに別のパッケージで使用されている場合、アナフォリック変数(上の場合は'it')が見つからないというエラーが出てしまう。
;;;;;;;;;;;;;;;;;;;; ;;;; user1パッケージ ;;;; a.whenマクロを使ったインライン関数を定義 (defpackage :user1 (:use :common-lisp) (:import-from :util a.when) (:export inline-fn)) (in-package :user1) (declaim (inline inline-fn)) (defun inline-fn (x) (a.when x (list :x it))) ; <- この'it'は、user1::it ;;;;;;;;;;;;;;;;;;;; ;;;; user2パッケージ ;;;; user1:inline-fnを使った関数を定義 (defpackage :user2 (:use :common-lisp) (:import-from :user1 inline-fn)) (in-package :user2) > (defun fn (y) (inline-fn y)) ; <- この場所でinline-fnが展開される ;;; 上の式(関数定義)を評価しようとすると、以下のようなエラーが出る ;;; ; in: LAMBDA NIL ; (USER1:INLINE-FN USER2::Y) ; --> BLOCK UTIL:A.WHEN LET WHEN IF PROGN ; ==> ; (LIST :X USER1::IT) ; ; caught WARNING: ; undefined variable: USER1::IT ; user1::it変数は定義されていない ; ; compilation unit finished ; Undefined variable: ; USER1::IT ; caught 1 WARNING condition ;;;;;;;;;;;;;;; ;;; fn関数の展開過程 ; 1] inline-fnが展開される (defun fn (y) (inline-fn y)) ; 2] a.whenマクロが展開される ; ※ この時に(intern "IT")も評価される (defun fn (y) (a.when y (list :x user1::it))) ; 3] 結果 (defun fn (y) (let ((user2::it y)) (when user2::it (list :x user1::it)))) ; <- user1::itは未定義
インライン宣言された関数内の'it'は、その場で評価されるので'user1::it'となるが、a.whenマクロ内の(intern "IT")は、インライン関数が実際に展開される時まで評価されないので、'user2::it'となってしまっている。
数日前に、この問題に遭遇した時は、とりあえず関数のインライン宣言を外すことで対処した。
改善策(?)
で、この問題の改善案。
今回および前回挙げた問題はいずれも(?)、'it'シンボルが属するパッケージがコロコロ変わってしまうことに関係していた。
それならば、キーワードに対するキーワードパッケージのように、アナフォリック変数専用のパッケージを作成してしまえば良いのではないかと思う。
後は、アナフォリックマクロの定義者・使用者の双方が、そのパッケージから変数(シンボル)を参照するようにすれば、パッケージに関する問題は起こらなくなるはず。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; アナフォリック変数用のパッケージ (defpackage :anaphoric (:use :common-lisp) (:nicknames :a) (:export it)) ; 'it'をエクスポート ;;;;;;;;;;;;;;;;;;;;;;; ;;;; a.whenの新しい定義 (defpackage :util (:use :common-lisp) (:export a.when)) (in-package :util) (defmacro a.when (expr &body body) `(let ((a:it ,expr)) ; anaphoric:it変数(シンボル)を使用する (when a:it ,@body))) ;;;;;;;;;; ;;;; user1 (defpackage :user1 (:use :common-lisp) (:import-from :util a.when) (:import-from :anaphoric it) ; anaphoric:itをインポート (:export inline-fn)) (in-package :user1) (declaim (inline inline-fn)) (defun inline-fn (x) (a.when x (list :x it))) ; <- この'it'は、anaphoric:it ;;;;;;;;;; ;;;; user2 (defpackage :user2 (:use :common-lisp) (:import-from :user1 inline-fn)) (in-package :user2) > (defun fn (y) (inline-fn y)) --> FN ; 特に問題無し > (fn 10) --> (:X 10)
当面は、この方法で試してみよう。
*1:インライン関数ではなく、マクロでも可