elisp宏扩展局部变量
elisp macro expansion local variable
我最近接触了 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")))
没有反引号,它将在宏扩展短语处进行评估,因此未插入的符号 "max" 将仅在该短语处创建。
而且uninterned符号"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))))
这里有两个max符号,一个绑定1,一个绑定10,while形式的表达式有两个max符号。
(while (<= max max)
while形式如何解析两个不同的符号"max"?
您正在查看打印名称,而不是符号标识。您有两个符号,都带有 "print name" max
,但身份不同。通常,我会建议使用 gensym
而不是 make-symbol
,但使用哪种方式并不重要。
将符号视为指向一个小结构的指针,其中存储了各种值。其中之一是 "name",当一个符号被驻留时,它被放置在一个特殊的结构中,所以你可以通过它的名字找到这个符号。您看到的是一个名为 max
的 interned 符号和一个名为 max
的 un-interned 符号。它们是不同的符号(即两个结构,因此指针不同),但是当您只看打印表示时,这并不明显。
快速演示,刚从 emacs“scratch”缓冲区中取出:
(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
,因为在读取表达式的过程中,您结束了使用 相同的 符号。
简短的回答是 (make-gensym "max") 创建一个名称为 max 的新符号。您还使用了名称为 max 的符号。它们具有相同的名称,因此,在 Emacs 中,print 的方式相同,但它们不是相同的符号。我们可以通过创建一个宏来轻松测试这一点,该宏会创建一个符号和 returns 一个将其与宏参数进行比较的形式:
(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
与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 方言的继承者。
我最近接触了 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")))
没有反引号,它将在宏扩展短语处进行评估,因此未插入的符号 "max" 将仅在该短语处创建。 而且uninterned符号"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))))
这里有两个max符号,一个绑定1,一个绑定10,while形式的表达式有两个max符号。
(while (<= max max)
while形式如何解析两个不同的符号"max"?
您正在查看打印名称,而不是符号标识。您有两个符号,都带有 "print name" max
,但身份不同。通常,我会建议使用 gensym
而不是 make-symbol
,但使用哪种方式并不重要。
将符号视为指向一个小结构的指针,其中存储了各种值。其中之一是 "name",当一个符号被驻留时,它被放置在一个特殊的结构中,所以你可以通过它的名字找到这个符号。您看到的是一个名为 max
的 interned 符号和一个名为 max
的 un-interned 符号。它们是不同的符号(即两个结构,因此指针不同),但是当您只看打印表示时,这并不明显。
快速演示,刚从 emacs“scratch”缓冲区中取出:
(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
,因为在读取表达式的过程中,您结束了使用 相同的 符号。
简短的回答是 (make-gensym "max") 创建一个名称为 max 的新符号。您还使用了名称为 max 的符号。它们具有相同的名称,因此,在 Emacs 中,print 的方式相同,但它们不是相同的符号。我们可以通过创建一个宏来轻松测试这一点,该宏会创建一个符号和 returns 一个将其与宏参数进行比较的形式:
(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
与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 方言的继承者。