Spec-Zone.ru › Elisp

Оценка аргументов макроса повторно

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

(defmacro for (var from init to final do &rest body)
  "Execute a simple \"for\" loop.
For example, (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) в этих позициях в вызове макроса.

Вот эквивалентное определение, упрощенное с помощью backquote:

(defmacro for (var from init to final do &rest body)
  "Execute a simple \"for\" loop.
For example, (for i from 1 to 10 do (print i))."
  `(let ((,var ,init))
     (while (<= ,var ,final)
       ,@body
       (inc ,var))))

Оба варианта этого определения (с backquote и без) страдают от недостатка, заключающегося в том, что 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)
  "Execute a simple for loop: (for i from 1 to 10 do (print i))."
  `(let ((,var ,init)
         (max ,final))
     (while (<= ,var max)
       ,@body
       (inc ,var))))

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

Copyright © 1990-1996, 1998-2022 Free Software Foundation, Inc.
Licensed under the GNU GPL license.
https://www.gnu.org/software/emacs/manual/html_node/elisp/Argument-Evaluation.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API