Emacs elisp宏展开局部变量

Emacs elisp宏展开局部变量,emacs,lisp,elisp,Emacs,Lisp,Elisp,我最近接触了elisp,并试图了解elisp宏的工作原理。 GNU教程中有一个for宏局部变量,我对宏扩展的工作原理感到困惑 (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 ((tempvar (make-symbol "max"))) `(let ((,var ,init)

我最近接触了elisp,并试图了解elisp宏的工作原理。 GNU教程中有一个for宏局部变量,我对宏扩展的工作原理感到困惑

(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 ((tempvar (make-symbol "max")))
    `(let ((,var ,init)
           (,tempvar ,final))
       (while (<= ,var ,tempvar)
         ,@body
         (inc ,var)))))
没有反引号,它将在宏展开短语处求值,因此不需要的符号“max”将仅在该短语处创建。 而不需要的符号“max”在运行时会丢失,它不应该正常工作吗

但实际上,它工作得很好。我尝试了以下方法:

(for max from 1 to 10 do (print max))
(macroexpand '(for max from 1 to 10 do (print max)))

(let ((max 1) (max 10)) (while (<= max max) (print max) (setq max (+ 1 max))))
其扩展如下:

(for max from 1 to 10 do (print max))
(macroexpand '(for max from 1 to 10 do (print max)))

(let ((max 1) (max 10)) (while (<= max max) (print max) (setq max (+ 1 max))))
(宏扩展)(最大值为1到10 do(打印最大值)))

(让((最大值1)(最大值10))(而(您查看的是打印名称,而不是符号标识。您有两个符号,都带有“打印名称”
max
,但标识不同。通常,我建议使用
gensym
,而不是
make-symbol
,但使用哪种方式并不重要

把一个符号想象成一个指向一个小结构的指针,里面存储着各种各样的值。其中一个是“名称”,当一个符号被插入时,它被放置在一个特殊的结构中,因此你可以通过它的名称找到该符号。你看到的是一个名为
max
的插入符号和一个名为
max
的未插入符号。它们是不同的符号(即,两个结构,指针因此不同),但当您仅查看打印的表示时,这并不明显

快速演示,刚从emacs“scratch”缓冲区中推出:


如果粘贴宏扩展结果的文本并对其求值,您将看到得到的是
t
,而不是
nil
,因为在读取表达式的过程中,最终得到的是相同的符号。

简短的回答是(将gensym设为“max”)创建一个名为max的新符号。您还使用了一个名为max的符号。这些符号具有相同的名称,因此在Emacs中,以相同的方式打印,但它们不是相同的符号。我们可以通过创建一个宏来轻松测试这一点,该宏将创建一个符号并返回一个将其与t进行比较的表单他认为:

(defmacro test-gensym (arg)
  (let ((max (make-symbol "max")))
    `(eq ',max ',arg)))
如果我们看一下宏扩展,我们可以看到我们正在比较两个名称相同的符号:

(print (macroexpand '(test-gensym max)))
;;=> (eq (quote max) (quote max))
(test-gensym max)
;;=> nil
但如果我们实际运行该代码,我们将看到所比较的值并不相同:

(print (macroexpand '(test-gensym max)))
;;=> (eq (quote max) (quote max))
(test-gensym max)
;;=> nil

与Common Lisp进行比较:在这里,您可以看到Common Lisp打印机使变量看起来不同。一个是您的
max
,另一个是一个不相关的
:max

CL-USER 70 > (pprint (macroexpand '(for max from 1 to 10 do (print max))))

(LET ((MAX 1) (#:MAX 10))
  (LOOP WHILE (<= MAX #:MAX) DO (PROGN (PRINT MAX) (INCF MAX))))

但这是常见的Lisp,而不是Emacs Lisp-尽管它们是相关的,因为它们都是早期Lisp方言的继承者。

有意义。对于第一个问题,你能解释一下吗?第一个let表单没有反引号,因此它将在扩展短语中进行评估(此时将创建未插入的符号)并且创建的未插入符号在运行时可能不存在。(想想这个例子:编译elisp代码,宏被展开,未插入符号“max”被创建。下次我直接从字节码运行它时,未插入符号“max”不存在,它是如何工作的?)。未插入的符号将存在(至少)只要有对它的引用。宏扩展代码有一个对未插入符号的引用。每次宏扩展时,您都会在扩展中得到一个不同的不需要的符号。“不需要”这仅仅意味着,准确地说,您无法通过其名称找到符号,而只能通过已对其进行引用。在Common Lisp中,将print circle设置为T通常会使用读取器变量打印具有相同名称的符号。在Emacs中,情况似乎并非如此。