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

via アナフォリックマクロとpacakge(2) - sileのブログ

it も一緒に export しといて使う側は use-package する、で個人的には困らないかなと思うのだけど、ライブラリとしてはまるっと use-package しなくてもマクロだけパッケージ名付きで `(anaph:awhen ...)` と使いたいことはあるような気もする。そうすると it の所属パッケージがアナフォリックマクロを使うコードのパッケージになっちゃったりしてめんどいことに。

で、アナフォリックマクロを使う側で使ってる it を拾ってきて使い回せばいんじゃね?と思ってみた。

(defpackage :anaph
  (:use :lisp))

(in-package :anaph)

(export '(awhen
          it)) ; いちおう export しておく

(defun flatten (tree)
  (labels ((rec (x acc)
             (cond ((null x) acc)
                   ((atom x) (cons x acc))
                   (t (rec (car x) (rec (cdr x) acc))))))
    (rec tree nil)))

(defun find-it-var (expr)
  (find-if (lambda (atom)
             (and (symbolp atom)
                  ;; case insensitiveェ...
                  (string-equal "IT" (symbol-name atom))))
    (flatten expr)))


(defmacro awhen (&whole whole test &body body)
  (let ((it-var (or (find-it-var whole)
                    'it)))
    (assert (symbolp it-var))
    `(let ((,it-var ,test))
       (if ,it-var (progn ,@body)))))

アナフォリックマクロの呼び出し式から "IT" という名前のシンボルを探して、見つかればそのシンボルを使う。見つからなかったら anaph:it を使う。

;; use-package して使う場合
(defpackage :test1
  (:use :lisp :anaph)) ; anaph を use してるので
=> #<package: test1>

(in-package :test1)
=> #<package: test1>

(awhen (+ 1 2)
  (list '(+ 1 2) it))  ; この it は anaph:it
=> ((+ 1 2) 3)
;; use-package せずに使う場合
(defpackage :test2
  (:use :lisp))        ; anaph を use してないので
=> #<package: test2>

(in-package :test2)
=> #<package: test2>

(anaph:awhen (+ 1 2)
  (list '(+ 1 2) it))  ; この it は test2::it だけど
=> ((+ 1 2) 3)


(macroexpand `(anaph:awhen (+ 1 2)
                (list '(+ 1 2) it)))
=> (let ((it (+ 1 2))) ; test2::it を使うように展開される
     (if it
       (progn
         (list '(+ 1 2)
               it))))
=> t

extended loop で使える it が似たようなことやってそうな予感。