`apply` или` funcall` для макросов вместо функций
В Лиспе аргументы функции оцениваются перед входом в тело функции. Макро-аргументы не обрабатываются.
Но иногда хочется внедрить в макрос фрагменты кода, хранящиеся в переменных. Это означает сначала вычисление аргумента для макроса, а затем применение макроса выбора к этому вычисленному результату.
Приходится прибегать к
(eval `(macro ,arg))
Чтобы добиться этого - но
eval
некорректно ведет себя в разных средах.
Лучше всего было бы, если бы можно было:
(apply macro (list arg))
или
(funcall macro arg)
Но поскольку макрос не является функцией, это не работает.
Можно ли добиться чего-то подобного? - Чтобы обойти эту проблему или сделать макрос доступным в пространстве имен функций?
Или мне не хватает других способов решения таких проблем?
Я пришел к этому вопросу, пытаясь ответить, как создать HTML из списка.но также в « Генерация TYPECASE с макросом в общем lisp» , « Оценить аргументы, переданные макросу, который генерирует функции в lisp» , и « Как преобразовать список в код / лямбду в схеме?».. Но я всегда думал, отвечая на них, как бы хорошо было
apply
или
funcall
-подобная функция, которая может принимать макросы.
1 ответ
Непонятно, что вы пытаетесь сделать, хотя почти наверняка вы в чем-то запутались. В частности, если вы коллируете внутри макрорасширений, то почти во всех случаях вы делаете что-то серьезно неправильное и очень опасное. Я никогда не могу вспомнить случай, когда мне нужны макросы, которые расширяются до вещей, в том числе, и я писал Lisp в течение очень и очень долгого времени.
При этом вот как вы вызываете функцию, связанную с макросом, и почему это очень редко то, что вы хотите сделать.
Макросы — это просто функции, доменом и областью действия которых является исходный код: они являются компиляторами с одного языка на другой. Вполне возможно вызвать функцию, связанную с макросом, но эта функция вернет исходный код , и что вам нужно будет сделать с этим исходным кодом, так это оценить его. Если вам нужна функция, которая работает с данными во время выполнения, которые не являются исходным кодом, тогда вам нужна эта функция, и вы не можете превратить макрос в эту функцию с помощью какого-то магического трюка, который кажется тем, что вы хотите сделать : фокуса нет и быть не может.
Так, например, если у меня есть макрос
(defmacro with-x (&body forms)
`(let ((x 1))
,@forms))
Затем я могу вызвать его макро-функцию для части исходного кода:
> (funcall (macro-function 'with-x)
'(with-x (print "foo")) nil)
(let ((x 1)) (print "foo"))
Но результатом этого является еще один фрагмент исходного кода: мне нужно его скомпилировать или оценить, и я ничего не могу сделать, чтобы обойти это.
Действительно, во всех (почти?) случаях это то же самое, что и ):
> (macroexpand-1 '(with-x (print "foo")))
(let ((x 1)) (print "foo"))
t
И вы, вероятно, можете написать
(defun macroexpand-1/equivalent (form &optional (env nil))
(if (and (consp form)
(symbolp (first form))
(macro-function (first form)))
(values (funcall (macro-function (first form)) form env)
t)
(values form nil)))
Итак, если результатом вызова макроса является исходный код, что вы делаете с этим исходным кодом, чтобы получить результат, который не является исходным кодом? Что ж, вы должны оценить это. И затем, поскольку оценщик все равно расширяет макросы для вас, вы можете просто написать что-то вроде
(defun evaluate-with-x (code)
(funcall (compile nil `(lambda ()
(with-x ,@code)))))
Таким образом, вам не нужно было вызывать функцию макроса в любом случае. И это не магический трюк, превращающий макросы в функции, работающие с данными, которые не являются исходным кодом: это ужасный ужас, целиком состоящий из взрывающихся частей.
Конкретный пример: CL-WHO
Похоже, что этот вопрос может возникнуть из-за этого, и основная проблема заключается в том, что это не то, чем занимается CL-WHO. В частности, было бы ошибкой думать, что что-то вроде CL-WHO — это инструмент для преобразования какого-то списка в HTML. Это не так: это инструмент для получения исходного кода языка, построенного на CL, но включающего способ выражения вывода HTML, смешанного с кодом CL, и компиляции его в код CL, который будет делать то же самое. Бывает так, что исходный код CL выражается в виде списков и символов, но CL-WHO на самом деле не об этом: это компилятор, если хотите, «языка CL-WHO» в CL.
Итак, давайте попробуем трюк, который мы пробовали выше, и посмотрим, почему это катастрофа:
(defun form->html/insane (form)
(funcall
(compile nil `(lambda ()
(with-html-output-to-string (,(make-symbol "O"))
,@form)))))
И вы могли бы, если бы вы не смотрели на это слишком внимательно, подумать, что эта функция действительно выполняет магический трюк:
> (form->html/insane '(:p ((:a :href "foo") "the foo")))
"<p></p><a href='foo'>the foo</a>"
Но это не так. Что произойдет, если мы вызовем этот совершенно безобидный список:
(:p (uiop/run-program:run-program "rm -rf $HOME" :output t))
Подсказка: не звони
CL-WHO — это реализация языка программирования, являющегося строгим надмножеством CL: если вы попытаетесь превратить его в функцию для преобразования списков в HTML, вы в конечном итоге получите что-то, связанное с тем же ядерным оружием, с которым вы возитесь каждый раз, когда вы вызываете , за исключением того, что ядерное оружие спрятано в запертом шкафу, где его не видно. Но его это не волнует: если вы его запустите, он все равно превратит все в радиусе нескольких миль в радиоактивный пепел и щебень.
Поэтому, если вам нужен инструмент, который будет преобразовывать списки — списки, которые не являются исходным кодом — в HTML, тогда напишите этот инструмент. CL-WHO может иметь смелость такого инструмента в его реализации, но вы не можете использовать его как есть.
И это та же самая проблема, с которой вы сталкиваетесь всякий раз, когда пытаетесь злоупотреблять макросами таким образом: результатом вызова функции макроса является исходный код Lisp , и для оценки этого исходного кода вам нужен или эквивалент . А также