我最近接触了elisp,并尝试了解elisp宏的工作原理。 GNU教程有一个chapter Surprising-local-Vars的宏局部变量,我对宏扩展如何工作感到困惑。
(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)))))
有两种let形式。第一个
(let ((tempvar (make-symbol "max")))
没有反引号,它将在宏扩展短语中得到评估,因此未加工符号&#34; max&#34;将仅在该短语中创建。 并且没有符号的符号&#34; max&#34;会在运行时丢失,它应该不正常吗?
但实际上,它运作良好。我尝试了以下方法:
(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,while表达式有两个最大符号。
(while (<= max max)
while表单如何解析两个不同的符号&#34; max&#34;?
答案 0 :(得分:3)
您正在查看打印名称,而不是符号标识。你有两个符号,都带有&#34;打印名称&#34; max
,但身份不同。通常情况下,我建议使用gensym
而不是make-symbol
,但实际上并不重要。
将符号视为指向小结构的指针,其中存储有各种值。其中一个是&#34; name&#34;,当一个符号被插入时,它被放置在一个特殊的结构中,因此您可以通过其名称找到该符号。您所看到的是名为max
的实习符号和名称为max
的非实习符号。它们是不同的符号(也就是说,两个结构和指针因此是不同的),但是当你只看一个印刷表示时,这并不明显。
快速演示,全新的电子邮件&#34; scratch &#34;缓冲液:
(defmacro demo (sym)
(let ((tmp (make-symbol "max")))
`(progn
(message "%s" (list ',tmp ',sym))
(eql ',tmp ',sym))))
demo
(macroexpand '(demo max))
(progn (message "%s" (list (quote max) (quote max))) (eql (quote max) (quote max)))
(demo max)
nil
如果您粘贴宏扩展产生的文本并对其进行评估,那么您会看到t
而不是nil
,因为在阅读表达式时,您将结束使用相同的符号。
答案 1 :(得分:1)
简短的回答是(make-gensym&#34; max&#34;)会创建一个名称为 max 的新符号。您还使用名称 max 的符号。它们具有相同的名称,因此,在Emacs中, print 以相同的方式,但它们不是相同的符号。我们可以通过创建一个宏来创建一个符号并返回一个将它与宏的参数进行比较的表单来轻松测试:
(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
答案 2 :(得分:0)
与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))))
如果我们告诉Common Lisp打印机支持打印循环数据结构,则打印机会标记未分隔符号的相同位置:#1=#:MAX
是带有打印机标签的符号。 #1#
就是对标记物的引用。
CL-USER 71 > (setf *print-circle* t)
T
CL-USER 72 > (pprint (macroexpand '(for max from 1 to 10 do (print max))))
(LET ((MAX 1) (#1=#:MAX 10))
(LOOP WHILE (<= MAX #1#) DO (PROGN (PRINT MAX) (INCF MAX))))
但那是Common Lisp,而不是Emacs Lisp - 虽然它们是相关的,但它们都是早期Lisp方言的继承者。