読者です 読者をやめる 読者になる 読者になる

アナフォリックマクロとpackage

common lisp

アナフォリックマクロをpackageに分けて使う場合のメモ書き。

カレントパッケージで、定義・使用する場合

(defmacro a.when (expr &body body)
  `(let ((it ,expr))
     (when it
       ,@body)))

> (a.when (member 2 '(1 2 3))
    (car it))
--> 2

特に問題なし。

定義と使用が別パッケージの場合

;;; マクロ定義パッケージ
(defpacakge :test
  (:use :cl)
  (:export :a.when))
(in-package :test)

;; ... a.when定義 ...


;;; 
(in-package :cl-user)
(use-package :test)

> (a.when (member 2 '(1 2 3))
    (car it))
--> エラー: 変数IT は定義されていない

エラーが出る。

解決策1: packageを明示的に指定
;;; ... defpackageとa.when定義 ...

(in-package :cl-user)
(use-package :test)

;; itのpackageを明示的に指定
> (a.when (member 2 '(1 2 3))
    (car test::it))
--> 2

毎回修飾するのは嫌なので却下。
そもそも、internalなsymbolを使っているのは問題。

解決策2: itをexport
;;; マクロ定義パッケージ
(defpacakge :test
  (:use :cl)
  (:export :a.when :it)) ; <- itもexport
(in-package :test)

;; ...a.when定義...

;;;
(in-package :cl-user)
(use-package :test)

> (a.when (member 2 '(1 2 3))
    (car it))
--> 2

itもexportしてあげれば、普通に実行できる。

ただ、この方法だと、アナフォリックマクロを定義しているpackageを必ずuseしなければいけない。後、symbol競合の問題も若干あるかもしれない

;;; ... 略(itをexportする版) ...

(in-package :cl-user)
;; (use-package :test) コメントアウト

> (test:a.when (member 2 '(1 2 3))
    (car it))
--> エラー: 変数IT は定義されていない
解決策3: itをマクロ展開時にintern
;;; マクロ定義パッケージ
(defpacakge :test
  (:use :cl)
  (:export :a.when)) 
(in-package :test)

;; itをsymbol-macroとして定義 
(define-symbol-macro it (intern "IT"))

(defmacro a.when (expr &body body)
  `(let ((,it ,expr)) ; it -> ,it
     (when it
       ,@body)))

;;;
(in-package :cl-user)

> (test:a.when (member 2 '(1 2 3))
    (car it))
--> 2

こんな感じで(マクロ展開時に)毎回internするようにすれば、アナフォリックマクロを定義しているパッケージをuse-packageするしないに関わらず、itをカレントパッケージのsymbolとして扱えるようになる。

この方法も問題があるかもしれないが、とりあえずは一番良さそうな気がする。