Почему этот макрос common lisp не оценивает первый s-exp?

#macros #common-lisp #ccl

#макросы #common-lisp #ccl

Вопрос:

Я изучил макрос define-easy-handler из пакета hunchentoot (который создает функцию с именем NAME) и заставил часть defun работать, но я не могу заставить этот макрос передавать ИМЯ в список с именем *observers* :

 (defmacro add-observer (name params amp;body body)
  ;; add NAME to the list *observers*
  `(push ,name *observers*)
  ;; define a lisp function with the name NAME
  ;; with key arguments given in PARAMS
  `(defun ,name (amp;key ,@(loop for p in params
                                collect p))
       ,@body)))
  

Примером вызова #'add-observer является:

 (add-observer below-80 (id blood-sugar)
              (when (< blood-sugar 80)
                (format t "Patient No: ~A is hypoglycemic." id))
  

ИМЯ функции определено и работает нормально, но ИМЯ не добавляется в список *observers* . Не имеет значения, помещаю ли я оба s-выражения в progn или нет. Макрорасширение четко показывает отсутствие вызова push с и без progn . Что я неправильно интерпретирую?

Редактировать

Когда я пытаюсь это сделать с помощью progn, например:

 `(progn
  (push ...
  (defn ...
  

он завершается с Unbound variable: below-80 ошибкой. И когда я возвращаю обратные кавычки обратно в #’push и #’defun, снова #’push не работает.

Комментарии:

1. Для меня очень хорошо работает с a progn , что вы наблюдаете, когда добавляете это?

2. @Madara Uchiha Я добавил, что не так с моим использованием progn. Вероятно, я неправильно использую progn.

3. Если это ваш настоящий макрос, то (loop for p in params collect p) это просто (copy-list params) . Однако копирование здесь кажется ненужным.

4. @coredump Спасибо за copy-list . Я, как правило, трачу больше времени на написание кода, чем на изучение существующих функций. Я добавил copy-list в свой арсенал.

5. @coredump О, и просто (amp;key ,@params) тоже достаточно. Я думаю, это то, что вы упомянули.

Ответ №1:

Макрос получает исходную форму и вычисляет первую форму результата: (push foo *observers*) и затем эта форма возвращается в нирвану сборщика мусора, поскольку она нигде не сохраняется, не возвращается ни одному вызывающему, не выполняется, … Это мусор, немедленно. Поэтому умный компилятор может даже удалить его…

Затем форма макроса вычисляет вторую форму (defun ...) . Эта форма возвращается из макроса, а затем выполняется позже.

Либо вы хотите выполнить первую форму во время развертывания макроса, тогда вам нужно будет сделать ее исполняемой — удалив обратную кавычку и запятую.

Или вы хотите включить его в сгенерированный исходный код, тогда вам нужно вернуть progn форму, которая включает все вложенные формы.

Вы также можете подумать о последствиях PUSH vs PUSHNEW . , когда форма макроса выполняется / расширяется несколько раз…

Нежелательная оценка символов при использовании макросов

Помните: символы являются переменными в коде Lisp. Если вы хотите рассматривать их как символы, как самих себя, тогда вам нужно заключить в кавычки символ или структуру данных, где они встречаются.

Скажем, ваша форма:

 (add-observer below-80 ...)
  

Это означает below-80 , что он не заключен в кавычки.

Теперь вы генерируете код, в котором символ также не заключен в кавычки.:

 (push below-80 ...)
  

Естественно, это пытается оценить переменную below-80 , которая, кажется, не связана в вашем коде.

Если вы хотите below-80 , чтобы во время оценки обрабатывался как символ, вы должны заключить его в кавычки : 'below-80 . Ваш сгенерированный код должен выглядеть следующим образом:

 (push 'below-80 ...)
  

Процитируйте его либо в своем коде

 (add-observer 'below-80 ...)
  

или с помощью макроса в расширение:

 (push 'below-80 ...)
  

Шаблон обратной кавычки для этого

 `(progn
   (push ',name ...)
   ...)
  

Комментарии:

1. Мое намерение — второй случай. Не могли бы вы показать правильное использование progn здесь?

2. @barerd: вам нужно посмотреть на расширение. Если вы не хотите что-то оценивать, вам нужно где-то процитировать это. Либо в коде, либо в расширении макроса.