В качестве примечания к ответу Сильвестра, вот версия вашего stack-push
, которая на первый взгляд выглядит правильно, но на самом деле имеет ужасную проблему (спасибо jkiiski за это!), За которой следует более простая версия, которая все еще есть проблема, и, наконец, вариант более простой версии, которой нет.
Вот начальная версия. Это согласуется с вашей подписью (она принимает либо один аргумент, который не может быть списком, либо список аргументов, и решает, что делать, основываясь на том, что видит).
(defmacro stack-push (stack element/s)
;; buggy, see below!
(let ((en (make-symbol "ELEMENT/S")))
`(let ((,en ,element/s))
(typecase ,en
(list
(setf ,stack (append (reverse ,en)
,stack)))
(t
(setf ,stack (cons ,en ,stack)))))))
Однако я бы больше соблазнил написать это с аргументом &rest
следующим образом. Эта версия проще, потому что она всегда делает одну вещь. Это все еще глючит.
(defmacro stack-push* (stack &rest elements)
;; still buggy
`(setf ,stack (append (reverse (list ,@elements)) ,stack)))
Эта версия может использоваться как
(let ((a '()))
(stack-push* a 1 2 3)
(assert (equal a '(3 2 1))))
например. И кажется, что это работает.
Множественная оценка
Но это не работает, потому что он может многократно оценивать вещи, которые не должны многократно оцениваться. Самый простой способ (я обнаружил) увидеть это - посмотреть, что такое расширение макроса.
У меня есть небольшая служебная функция для этого, называемая macropp
: она просто вызывает macroexpand-1
столько раз, сколько вы просите, довольно красиво печатая результат. Чтобы увидеть проблему, нужно дважды развернуть: сначала развернуть stack-push*
, а затем посмотреть, что произойдет с полученным seetf
. Второе расширение зависит от реализации, но вы можете увидеть проблему. Этот образец взят из Clozure CL, который имеет особенно простое расширение:
? (macropp '(stack-push* (foo (a)) 1) 2)
-- (stack-push* (foo (a)) 1)
-> (setf (foo (a)) (append (reverse (list 1)) (foo (a))))
-> (let ((#:g86139 (a)))
(funcall #'(setf foo) (append (reverse (list 1)) (foo (a))) #:g86139))
И вы видите проблему: setf
ничего не знает о foo
, поэтому он просто звонит #'(setf foo)
. Он тщательно следит за тем, чтобы подчиненные формы оценивались в правильном порядке, но просто оценивает вторую подчиненную форму очевидным способом, в результате чего (a)
оценивается дважды , что неверно: если оно имеет побочные эффекты, то они будут происходить дважды.
Так что исправление для этого - использовать define-modify-macro
, задача которого - решить эту проблему. Для этого вы определяете функцию, которая составляет стек, а затем используете define-modify-macro
для создания макроса:
(defun stackify (s &rest elements)
(append (reverse elements) s))
(define-modify-macro stack-push* (s &rest elements)
stackify)
А сейчас
? (macropp '(stack-push* (foo (a)) 1) 2)
-- (stack-push* (foo (a)) 1)
-> (let* ((#:g86170 (a)) (#:g86169 (stackify (foo #:g86170) 1)))
(funcall #'(setf foo) #:g86169 #:g86170))
-> (let* ((#:g86170 (a)) (#:g86169 (stackify (foo #:g86170) 1)))
(funcall #'(setf foo) #:g86169 #:g86170))
И вы можете видеть, что теперь (a)
оценивается только один раз (а также что вам нужен только один уровень макроразложения).
Еще раз спасибо jkiiski за указание на ошибки.
macropp
Для полноты, вот функция, которую я использую для красивой распечатки макроса. Это просто взломать.
(defun macropp (form &optional (n 1))
(let ((*print-pretty* t))
(loop repeat n
for first = t then nil
for current = (macroexpand-1 form) then (macroexpand-1 current)
when first do (format t "~&-- ~S~%" form)
do (format t "~&-> ~S~%" current)))
(values))