Common Lisp: Как построить выражение цикла с помощью макроса? - PullRequest
2 голосов
/ 10 февраля 2012

Это связанный вопрос , своего рода продолжение.

Допустим, я пытаюсь построить выражение цикла с помощью макросов, в которых полученное выражение цикла имеет видзависит от того, является ли параметр списком:

(defmacro testing-loop (var)
   `(eval (append '(loop for x from 0 to 5)
            (when (consp ,var) '(and y in ,var))            
            '(collect)            
            (if (consp ,var) '(y) '(x))))

Кажется, это работает:

CL-USER> (testing-loop 2)
(0 1 2 3 4 5)
CL-USER> (testing-loop (list 5 6 7))
(5 6 7)

Но при применении этого макроса в лексическом замыкании он ломается:

CL-USER> (let ((bar (list 1 2 3)))
           (testing-loop bar))

, который выбрасывает неопределенную переменную: BAR

Я ожидал, что testing-loop будет макрорасширяться в лексическую область, где находится бар?

1 Ответ

4 голосов
/ 10 февраля 2012

@ mck, я понимаю, почему вы хотите использовать eval сейчас. Но это очень грязное и медленное решение, как я уже упоминал в своем ответе на предыдущий вопрос. Классический на Лиспе говорит об этом eval:

"Обычно не рекомендуется вызывать eval во время выполнения по двум причинам:

  1. Это неэффективно: eval передается необработанный список, и любой должен скомпилировать его в определить или оценить его в переводчике. В любом случае медленнее, чем компиляция код заранее и просто вызывая его.

  2. Он менее мощный, потому что выражение оценивается без лексического контекста. Среди прочего, это означает, что вы не можете ссылаться на обычные переменные, видимые вне вычисляемого выражения.

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

В этом случае самое простое:

(defmacro testing-loop (var)
  (let ((g (gensym)))
   `(let ((,g ,var))
      (if (consp ,g)
        (loop for x from 0 to 5 collect x)
        (loop for x from 0 to 5 and y in ,g collect y)))))

Я знаю, что вы хотите выделить общее loop for x from 0 to 5 (которое на самом деле не нужно во второй ветке в любом случае). Но loop сам по себе макрос, который преобразуется во время компиляции в эффективный код низкого уровня. Таким образом, вызов loop должен быть построен во время компиляции , используя значения, которые доступны во время компиляции. Вы не можете просто вставить туда (if), который предназначен для оценки во время выполнения.

Если вы действительно не хотите повторять loop for x from 0 to 5, вы можете сделать что-то вроде:

(let ((a '(loop for x from 0 to 5)))
  `(if (consp ,var)
       (,@a collect x)
       (,@a and y in ,var collect y)))

Это просто чтобы дать вам идею; если вы действительно делаете это, убедитесь, что gensym!

Хороший урок, который можно извлечь из этого: когда вы пишете макросы, вы должны четко помнить, что происходит во время компиляции и что происходит во время выполнения. Макрос, который вы написали с помощью eval, динамически компилирует макрос loop, каждый раз, когда он запускается , на основе возвращаемого значения consp. Вы действительно хотите скомпилировать 2 разных макроса loop один раз и просто выбрать правильный во время выполнения.

...