Макросы - это преобразование кода. Таким образом, расширение может произойти уже тогда, когда вы оцениваете defun
. например.
(defun test-1 (condition)
(macro-test-1 #'(lambda () (error 'simple-type-error)) condition))
;; becomes this
(defun test-1 (condition)
(handler-case (funcall #'(lambda nil (error 'simple-type-error)))
(condition (c)
(declare (ignore c)) (format t "~a" 'why?)))
Теперь давайте просто скажем, что вы хотите проверить случай обработчика для simple-type-error
. Вы напишите это так:
(handler-case expression
(simple-type-error ()
(format t "~a" 'why?)))
не
(handler-case expression
('simple-type-error ()
(format t "~a" 'why?)))
Например. handler-case
- это синтаксис, и это место не может иметь переменную, которая может быть оценена для некоторой ошибки, но должна быть спецификатором типа, и это, вероятно, обрабатывается системой во время компиляции. Вот почему вы получаете, что condition
никогда не используется, так как ваш handler-case
проверяет тип с именем condition
, а не тот, который вы отправили в качестве аргумента condition
.
Создание test-1
макроса фактически передает division-by-zero
в качестве символа macro-test-1
, и в результате получается:
(handler-case (funcall #'(lambda nil (error 'simple-type-error)))
(division-by-zero (c)
(declare (ignore c))
(format t "~a" 'why?)))
Это также означает, что ошибки должны быть известны во время компиляции, так как вы не можете передать макросу значения в переменных. Вот почему это работает, поэтому, когда вы захотите, чтобы какой-то пользователь указал, какая ошибка будет действовать, вы не сможете сделать это с вашим решением.
EDIT
В up2
вы спрашиваете, почему это работает:
(defun test-1 (fn value)
(macro-test-1 fn value))
Итак, мы просто выясним, что на самом деле спасено:
(macroexpand-1 '(macro-test-1 fn value))
; ==> (funcall fn value)
; ==> t
Таким образом, ваша функция становится такой:
(defun test-1 (fn value)
(funcall fn value))
handler-case
был синтаксисом, который не принимал переменные или выражения в нужном вам месте, и поэтому он не работал, но он, конечно, будет работать для всех функций, включая funcall
, так как он оценивает все аргументы.
Чтобы показать вам другой пример того, что не работает, это case
:
(defun check-something (what result default-value value)
(case value
(what result)
(otherwise default-value)))
case
- это макрос, так что на самом деле происходит. Мы можем сделать macroexpand-1
, чтобы увидеть:
(macroexpand-1
'(case value
(what result)
(otherwise default-value))
)
; ==> (let ((tmp value))
; (cond ((eql tmp 'what) result)
; (t default-value)))
; ==> t
Макрос ожидает, что значения регистра являются литералами и, следовательно, заключают их в кавычки, чтобы они никогда не оценивались. Результирующая функция, которую вы ясно видите, what
никогда не используется, как и condition
:
(defun check-something (what result default-value value)
(let ((tmp value))
(cond ((eql tmp 'what) result)
(t default-value))))
Макросы должны абстрагироваться от синтаксиса. Вы должны иметь возможность писать код без макроса и скорее видеть, что это повторяющийся шаблон, а не добавлять абстракцию, которая переписывает вашу упрощенную версию в полную версию. Если это не может быть сделано для начала, его нельзя переписать как макрос.
То же самое для функций. Вся причина, по которой у нас есть макросы, заключается в контроле над оценкой. Хороший пример того, что не может быть записано как функция, это if
:
(defun my-if (predicate consequence alternative)
(cond (predicate consequence)
(t alternative)))
(my-if t 'true 'false) ; ==> true
(my-if nil 'true 'false) ; ==> false
Но так как функции всегда оценивают свои аргументы, вы не можете сделать это:
(defun factorial (n)
(my-if (<= n 1)
1
(* n (factorial (1- n)))))
Это никогда не остановится, поскольку, будучи функцией, все 3 аргумента всегда оцениваются, и (* n (factorial (1- n))))
выполняется, даже если n
отрицателен и будет иметь бесконечную рекурсию. Вместо этого использование макроса заменит my-if
полученным cond
, и оба cond
и if
не будут оценивать все свои аргументы, а не будут иметь короткие замыкания на том, который соответствует истинному предикату.
Вы можете использовать macroexpand-1
, чтобы проверить, действительно ли ваш код правильный. Вы должны быть в состоянии заменить вход с weput. При использовании macroexpand
применяет расширение, пока оно больше не будет расширяться. Например. cond
также будет расширен до вложенных if
.
РЕДАКТИРОВАТЬ 2
С up3
:
(defun test-1 (fn value)
(macro-test-1 fn value))
Это та же проблема. Функция макроса получает fn
и value
в качестве привязок, и в результате получается:
(defun test-1 (fn value)
(fn value))
Это могло бы сработать в Схеме, но в Common Lisp символы в последовательности операторов отличаются от других позиций. Таким образом, когда CL пытается найти функцию fn
, она никогда не выглядит близко к переменной fn
. Единственный способ решить эту проблему - использовать funcall
, и тогда вам вообще не нужен макрос:
(defun with-1 (fn value)
(funcall fn value 1))
(with-1 #'+ 10) ; ==> 11
Обратите внимание на префикс #'
. Это коротко для (function ...)
, поэтому на самом деле (function +)
. function
- это специальная форма, которая принимает символ аргумента и получает значение из пространства имен функции.
С помощью eval
вы можете делать много вещей, но это идет с ценой.Он не будет оптимизирован и, возможно, даже просто интерпретирован, и может привести к ошибкам компиляции во время выполнения, а также к угрозам безопасности.Хорошим примером был интерактивный интерактивный рубин, который только что сделал eval
, и он работал хорошо, пока кто-то не оценил код, который удалил все системные файлы.eval
считается вредным и даже злом.В своей профессиональной карьере я видел, как eval
использовался 3 раза специально.(2 раза в PHP, один в requirejs).В один из тех случаев я бросил вызов писателю, что может быть лучший способ сделать это.Конечно, и handler-case
, и case
будут работать с eval
, поскольку вычисляемый код будет иметь правильный формат, но вы потеряете лексическую область видимости.например.
(let ((x 10))
(eval '(+ x 1)));
; *** EVAL: variable X has no value
Вы можете быть умным и делать это:
(let ((x 10))
(eval `(+ ,x 1))) ; ==> 11
но что, если это был список или что-то еще, не самооценочное?
(let ((x '(a b)))
(eval `(cons '1 ,x)))
; *** undefined function: a
Таким образомeval
также имеет свои вызовы.Держите подальше для других целей, чем образование.