我在 Lisp 中“查看”宏的方式如下:每当我看到宏调用 (mymacro arg1 arg2 ... argn)
时,我认为它与 ( 相同eval (myfun (quote arg1) (quote arg2) ... (quote argn)))
,其中 myfun 是一个函数,其主体与 mymacro.I 的主体完全相同想象一下这是一种过于简单化的理解宏的方式。但我很难找到失败的例子。
那么,是否有 s 表达式 BODY 和 ARG 的示例,使得给定以下 mymacro
和 myfun
定义,
(defmacro mymacro (param) BODY)
(defun myfun (param) BODY)
表达式
(mymacro ARG)
和 (eval (myfun (quote ARG)))
不评估出相同的结果吗?
PS:我只使用一个参数,因此写这个问题更容易,但我显然对具有多个参数的示例感到满意。
PS:我对最后的语义差异更感兴趣,而不是实现细节、性能差异等。
最佳答案
But I'm having trouble finding an example where it fails.
(defmacro plus-mac (left right)
`(+ ,left ,right))
(defun plus-expander (left right)
`(+ ,left ,right))
然后:
(let ((a 3) (b 4))
(plus-mac a b)) ;; -> 7, works!
(let ((a 3) (b 4))
(eval (plus-expander 'a 'b))) ;; fail: variables a and b not defined
您在某处读到过类似“宏被替换为在其位置上求值的代码(由其扩展器函数生成)”的宏描述,并且过于字面理解了它。
宏被替换,但扩展不被评估; eval
调用未插入到代码中的该位置。
首先,宏通常在代码执行之前展开。这两个 Action 可以完全分开。例如,宏可能会在开发人员的构建计算机上展开,而评估则在程序在用户的目标计算机上运行时进行。
即使在计算表达式之前立即展开宏,该计算也不是通过插入代码中的 eval
调用来完成的。正如我上面的示例所示,这不能正确地与词法作用域配合使用。
最后,宏(我们正在讨论的不卫生的、Common-Lisp 风格的宏,它们执行简单的替换)并不像问题中想象的那样在幕后工作。像 (mac a b (c d (e)))
这样的宏调用会导致调用扩展器函数。该函数有两个参数:整个宏形式和宏时间环境。将宏语法分为参数 a
、b
、c
、d
和 e
由 defmacro
编写的代码完成。扩展器看起来像:
(lambda (form env)
(destructuring-bind (#:operator a b (c d (e))) form
... body goes here ...))
这仍然不准确,因为 destructuring-bind
不会产生良好的诊断。解构 lambda 列表和宏 lambda 列表类似,但不相同。这仅用于说明目的,以表明 defmacro
在宏主体周围插入解构代码;该代码通过一个参数接收整个宏调用形式,并设置保存命名片段的局部变量。
如果您想了解幕后情况,请尝试展开 defmacro
调用以查看它生成的代码:
(macroexpand '(defmacro mac (a b (c d (e))) (list a b c d e)))
关于macros - (mymacro arg) 和 (eval (myfunc (quote arg))) 有什么区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77090618/