Типичная проблема с макросами - понять, что в целом они имеют дело с кодом: они получают код и производят код.Обычно они не знают значения переменных, потому что код еще не был запущен.
Например, представьте:
(let ((n 10))
(with-open-chapter-file (out ("pack" :chapter-number n))
(format t "Hey!")))
Теперь в макросе нет общего способа узнатькаково значение n
.Когда форма макроса раскрывается во время компиляции, она видит n
и ничего более.
Теперь, когда у вас есть действительное число в коде, макрос видит это число как часть источника:
(with-open-chapter-file (out ("pack" :chapter-number 10)
(format t "Hey!")))
Теперь мы можем спросить нас, имеет ли смысл макрос распознавать число во время раскрытия макроса и вычислять что-то во время раскрытия макроса?Это своего рода оптимизация, и она может не стоить того.Теперь компилятор может обнаружить, что он является константой и может быть преобразован во время компиляции ...
Таким образом, в вашем примере было бы хорошо преобразовать аргумент в строку во время выполнения вместо того, чтобы делать это ввремя макроразложения.
Теперь предположим, что код выглядит следующим образом:
(defmacro with-open-chapter-file
((streamvar (component
&key
(type "lisp")
(directory "/foo/")
chapter-number))
(&body body))
(when (numberp chapter-number)
(setf chapter-number (write-to-string chapter-number)))
`(let ((component ,component)
(type ,type)
(directory ,directory)
(chapter-number ,chapter-number))
(when (numberp chapter-number)
(setf chapter-number (write-to-string chapter-number)))
(with-open-file
(,streamvar (make-pathname
:name (if chapter-number
(format nil
"chapter-~a-~a"
chapter-number
component)
component)
:type type
:defaults directory)
:direction :output)
,@body)))
Теперь мы можем сделать это:
a) с помощью n
CL-USER 6 > (pprint (macroexpand-1 '(with-open-chapter-file (out ("pack" :chapter-number n))
(format t "Hey!"))))
(LET ((COMPONENT "pack") (TYPE "lisp") (DIRECTORY "/foo/") (CHAPTER-NUMBER N))
(WHEN (NUMBERP CHAPTER-NUMBER) (SETF CHAPTER-NUMBER (WRITE-TO-STRING CHAPTER-NUMBER)))
(WITH-OPEN-FILE (OUT
(MAKE-PATHNAME :NAME
(IF CHAPTER-NUMBER
(FORMAT NIL "chapter-~a-~a" CHAPTER-NUMBER COMPONENT)
COMPONENT)
:TYPE
TYPE
:DEFAULTS
DIRECTORY)
:DIRECTION
:OUTPUT)
FORMAT
T
"Hey!"))
и б) с 10
CL-USER 7 > (pprint (macroexpand-1 '(with-open-chapter-file (out ("pack" :chapter-number 10))
(format t "Hey!"))))
(LET ((COMPONENT "pack") (TYPE "lisp") (DIRECTORY "/foo/") (CHAPTER-NUMBER "10"))
(WHEN (NUMBERP CHAPTER-NUMBER) (SETF CHAPTER-NUMBER (WRITE-TO-STRING CHAPTER-NUMBER)))
(WITH-OPEN-FILE (OUT
(MAKE-PATHNAME :NAME
(IF CHAPTER-NUMBER
(FORMAT NIL "chapter-~a-~a" CHAPTER-NUMBER COMPONENT)
COMPONENT)
:TYPE
TYPE
:DEFAULTS
DIRECTORY)
:DIRECTION
:OUTPUT)
FORMAT
T
"Hey!"))
Но поскольку format
выполняет преобразование в любом случае во время печати, мы можем удалить всю эту логику преобразования ...
(defmacro with-open-chapter-file
((streamvar (component
&key
(type "lisp")
(directory "/foo/")
chapter-number))
(&body body))
`(let ((component ,component)
(type ,type)
(directory ,directory)
(chapter-number ,chapter-number))
(let ((name (if chapter-number
(format nil
"chapter-~a-~a"
chapter-number
component)
component)))
(with-open-file (,streamvar (make-pathname
:name name
:type type
:defaults directory)
:direction :output)
,@body))))
Теперь вам нужно убедиться, что component
, type
... не являются нежелательными переменными времени выполнения, которые затем были видны из кода тела ...