Есть ли способ заставить макрос выполнить дополнительную оценку перед возвратом его результата? - PullRequest
5 голосов
/ 11 мая 2019

Я пытаюсь заставить мой макрос выполнить дополнительную оценку своего результата перед его возвратом.Можно ли это сделать без eval?

Я пытаюсь решить проблему в упражнении 4 ниже:

Определить макрос nth-expr, который принимает целое число n и произвольное количество выражений, вычисляет n-е выражение и возвращает его значение.Это упражнение легко выполнить, если предположить, что первый аргумент является целым литералом.

4.Как упражнение 3, но предположим, что первый аргумент является выражением, которое нужно вычислить.

Легко получить макрос, чтобы выбрать правильное выражение:

(defmacro nth-expr% (n &rest es)
  `(nth ,n ',es))

CL-USER> (defvar i 1)
I
CL-USER> (nth-expr% (1+ i) (+ 2 3) (- 4 3) (+ 3 1))
(+ 3 1)

Выражение (+ 3 1) - это то, что нам нужно, но мы хотим, чтобы макрос оценил его до 4, прежде чем вернуть.

Конечно, это можно сделать с помощью eval:

(defmacro nth-expr%% (n &rest es)
  `(eval (nth ,n ',es)))

CL-USER> (nth-expr%% (1+ i) (+ 2 3) (- 4 3) (+ 3 1))
4

Ноесть ли другой способ?

Такое ощущение, что решение должно заключаться в том, чтобы поместить тело nth-expr% в макрос помощника, и чтобы макрос верхнего уровня содержал только вызов в кавычки без кавычек:

(defmacro helper (n es)
  `(nth ,n ',es))

(defmacro nth-expr (n &rest es) ; doesn't work!
  (helper n es))

Идея состоит в том, что вызов helper вернет (+ 3 1), и тогда это будет расширение вызова до nth-expr, который во время выполнения оценивается в 4. Он взрывается,конечно, потому что к N и ES относятся как к литералам.

Ответы [ 2 ]

8 голосов
/ 11 мая 2019

Это не так просто.

Использование eval не годится, поскольку eval не оценивает код в локальной лексической среде.

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

(nth-expression
   foo
 (bar)
 (baz))

Итак, мы могли бы подумать о коде, который делает это:

(case foo
  (0 (bar))
  (1 (baz)))

CASE оценивает foo и затем использует результат, чтобы найти предложение, которое имеет то же значение в своей голове. Последующие формы этого пункта будут затем оценены.

Теперь нам нужно написать код, который расширяет первое в второе.

Это была бы очень простая версия:

(defmacro nth-expression (n-form &body expressions)
  `(case ,n-form
     ,@(loop for e in expressions
             and i from 0
             collect `(,i ,e))))

Вопрос: какие могут быть недостатки использования CASE таким образом?

1 голос
/ 13 мая 2019

Кнуто: Райнер Йосвиг, возможно, просит вас подумать о том, как работает утверждение дела. А именно, что после оценки ключевой формы (т. Е. Первого аргумента) она будет сравниваться последовательно с ключом в каждом предложении, пока не будет найдено совпадение. Сравнение может занять много времени, если есть много предложений. Вы можете узнать это, внимательно прочитав запись для case в Гиперспеке (как он неоднократно настаивал на этом):

Форма ключа или место ключа оценивается для создания тестового ключа. Каждый из нормальные предложения затем рассматриваются по очереди.

Также обратите внимание, что построение множества предложений case добавит время для расширения и компиляции макроса во время компиляции.

Что касается использования eval в nth-expr%%, вы все равно можете добиться эффекта eval, переключившись на apply:

(defmacro nth-expr%% (n &rest es)
  `(let ((ne (nth ,n ',es)))
     (apply (car ne) (cdr ne))))

Но см. «Затвор утечки» в http://www.gigamonkeys.com/book/macros-defining-your-own.html, чтобы узнать о более надежной обработке.

В целом, более эффективный способ обработки выражений - это простой вектор, а не список. (Постановка задачи не исключает векторное представление.) Хотя nth и case предполагают поиск по выражениям один за другим, такая функция, как aref или svref, может напрямую индексировать ее. Предполагая, что вектор выражений передается в макрос вместе с индексом, возможно, сначала требуется (coerce expressions 'simple-vector), если список, то результат может быть вычислен за постоянное время независимо от количества выражений:

(defmacro nth-expr%%% (n es)
  `(let ((ne (svref ',es ,n)))
     (apply (car ne) (cdr ne))))

так что теперь

(defvar i 1)

(nth-expr%%% (1+ i) #((+ 2 3) (- 4 3) (+ 3 1))) -> 4
...