`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 , и для оценки этого исходного кода вам нужен или эквивалент . А также это не только не страшное решение почти любой проблемы: это еще и ядерное оружие. Возможно, есть проблемы, для которых ядерное оружие является хорошим решением, но их очень мало.

Другие вопросы по тегам