14.5.2 Многократное вычисление аргументов макроса

При определении макроса необходимо обращать внимание на то, сколько раз будут вычисляться аргументы при выполнении расширения. Следующий макрос (используемый для упрощения итераций) иллюстрирует эту проблему. Этот макрос позволяет нам записывать конструкцию цикла for.

(defmacro for (var from init to final do &rest body)
  "Выполняет простой цикл \"for\".
Например, (for i from 1 to 10 do (print i))."
  (list 'let (list (list var init))
        (cons 'while
              (cons (list '<= var final)
                    (append body (list (list 'inc var)))))))

(for i from 1 to 3 do
   (setq square (* i i))
   (princ (format "\n%d %d" i square)))
→
(let ((i 1))
  (while (<= i 3)
    (setq square (* i i))
    (princ (format "\n%d %d" i square))
    (inc i)))

     -|1       1
     -|2       4
     -|3       9
⇒ nil

Аргументы from, to и do в этом макросе являются синтаксическим сахаром; они полностью игнорируются. Идея в том, чтобы вы писали слова-заполнители (такие как from, to и do) на этих позициях в вызове макроса.

Вот эквивалентное определение, упрощённое с использованием обратного апострофа:

(defmacro for (var from init to final do &rest body)
  "Выполняет простой цикл \"for\".
Например, (for i from 1 to 10 do (print i))."
  `(let ((,var ,init))
     (while (<= ,var ,final)
       ,@body
       (inc ,var))))

Обе формы этого определения (с обратным апострофом и без) страдают от недостатка: final вычисляется на каждой итерации. Если final является константой, это не проблема. Если же это более сложная форма, например (long-complex-calculation x), это может значительно замедлить выполнение. Если final имеет побочные эффекты, то выполнение его более одного раза, вероятно, некорректно.

Хорошо продуманное определение макроса предпринимает шаги для избежания этой проблемы, создавая расширение, которое вычисляет выражения аргументов ровно один раз, если только повторное вычисление не является частью предполагаемого назначения макроса. Вот правильное расширение для макроса for:

(let ((i 1)
      (max 3))
  (while (<= i max)
    (setq square (* i i))
    (princ (format "%d      %d" i square))
    (inc i)))

Вот определение макроса, которое создаёт это расширение:

(defmacro for (var from init to final do &rest body)
  "Выполняет простой цикл for: (for i from 1 to 10 do (print i))."
  `(let ((,var ,init)
         (max ,final))
     (while (<= ,var max)
       ,@body
       (inc ,var))))

К сожалению, это исправление порождает другую проблему, описанную в следующем разделе.