在下面的函数中,我用mac-1替换了宏,mac-1只是返回了它起作用的参数的值,但是mac-2抛出了类型错误。有人可以解释会发生什么并导致此错误吗?为什么x从fun-2传递给mac-2是一个文字符号x
而不是整数?
(defmacro mac-1 (x) x)
(defun fun-1 (x) (mac-1 x))
(fun-1 3) ; => 3
(defmacro mac-2 (x) (+ x 3))
(defun fun-2 (x) (mac-2 x))
(fun-2 3)
执行带有错误的已编译表单。 形成: (MAC-2 X) 编译时错误: (MAC-2 X)宏扩展期间。使用 BREAK-ON-SIGNALS 进行拦截。
值 X 不是类型 数 当绑定SB-KERNEL :: X
时
答案 0 :(得分:7)
重命名事物:
(defmacro mac-2 (source-code-expression)
(declare (type (or number list symbol string) ; actually T,
; since all types are allowed
source-code-expression))
(+ source-code-expression 3)) ; here we have a type problem
这仅适用于数字。但是:例如,一个符号不能加3。
请记住:宏接受任意表达式并创建新表达式。在宏扩展时,应该看到的是源代码,而不是值。
从
x
传递到fun-2
的{{1}}是源代码(数据)吗?并在对mac-2
求值后立即使用此数据(符号x
)扩展宏,并将扩展项放入fun-2
的主体中?逐步,我开始了解我还不了解宏!
几乎:在评估函数之前,也可以扩展该宏。例如,当您编译函数时,宏将展开->如果宏需要运行时值,显然这是不可能的。
宏完全替代了更多的动态机制,因为它们可以编译为有效的代码,而在运行时则不需要宏扩展。
将fun-2
传递给宏的函数的心理模型并不是真的有用。
更像是第一个基本概念:将宏形式转换为某种新的表达式-在编译期间是编译的实现,在评估过程中是解释的版本。
x
是宏的名称mac-2
是宏形式,因为它是复合形式,而(mac-2 x)
是宏的名称。通常所有将要求值的表达式都称为 form :函数形式,宏形式,特殊形式,lambda形式,符号和自求值对象。
您能解释一下宏中的类型声明是做什么用的吗?
只是要弄清楚作为参数类型可以期待什么。
不要在代码中使用它,而应使用mac-2
(请参见下文)。
check-type
通常在可移植代码中,当您的宏期望某个参数具有某种类型或形状时,则可能需要添加(let ((x 100))
(mac-2 1) ; the macro sees a number
(mac-2 x) ; the macro sees a symbol ! Not a number!
(mac-2 "x") ; the macro sees a string
(mac-2 (+ x 20)) ; the macro sees a list ! Not a number!
; and so on for other data objects
)
调用以使其清楚。假设我们要编写一个宏来定义 planet ,我们需要一个名称,该名称应该是符号或字符串:
check-type
上面将在宏扩展时检查“名称”值的类型。
答案 1 :(得分:6)
(defmacro mac-1 (x) x)
之所以有效,是因为定义了fun-1
后,它会将x
扩展为x
。基本上是个小问题。它适用于任何表达式,因为它在编译时不进行计算。
(defmacro mac-2 (x) (+ x 3))
仅在语法x
是文字时才有效。例如。 (mac-2 3)
转换为6
,但是在您的函数中将其赋予x
。请记住,宏会转换语法,因此您正在执行(+ 'x 3)
(绑定x
具有fun-2
中给出的值,它是符号x
)。发生扩展时,变量x
甚至不存在,因此甚至没有值。
如果期望表达式的结果成为数字,则可以在mac-2
存在时使x
进行计算。例如。
(defmacro mac-2 (x)
`(+ ,x 3))
创建fun2
时,它将扩展宏,并且不会立即尝试进行计算,但是该函数将存储为:
(defun fun-2 (x) (+ x 3))
当您致电fun-2
时,x
将存在,(+ x 3)
才有意义。在创建函数时执行(+ x 3)
并不可行,因为那时宏获得x
而x
尚不存在。
答案 2 :(得分:3)
要了解Lisp中的宏,关键是要指定从源到源的转换:一个宏会获取一点Lisp源代码并返回另外一点Lisp源代码,其中源代码(在Common Lisp和其他相关Lisps中)表示为s表达式。这种扩展发生在代码被评估之前 –从概念上讲,它是在准备评估代码的过程中进行的,通常是在编译过程中的一部分。
尤其是类似
的表达式(defmacro foo (x)
...)
定义一个完全普通的Lisp函数,该函数的参数是一大段源代码(一种形式),并且需要返回另一段源代码。
使用defmacro
定义宏时,通常看不到函数获得的形式,因为defmacro
根据您给出的arglist将其分开,并仅向您展示您所需要的部分关心。但是,宏参数列表中有一个特殊的选项&whole
,它可以使您看到整个表单。
因此,我们可以定义您的宏的版本,以显示其调用方式以及源代码块是什么:
(defmacro mac-1 (&whole form x)
(format *debug-io* "~&mac-1 called with ~S~%" form)
x)
这与您的mac-1
相同,但是它将在宏扩展时打印调用它的形式。
现在我们可以定义一个使用此功能的函数:
(defun fun-1 (x)
(mac-1 x))
与mac-1
相对应的函数被调用时,其调用次数取决于实现。在仅编译器的实现中,当评估该形式时,它可能至少被调用一次。在带有解释器的实现中,稍后可能会调用它。在我使用的实现中(LispWorks),调用它的简单方法是显式编译该函数:
> (defun fun-1 (x) (mac-1 x))
fun-1
> (compile 'fun-1)
mac-1 called with (mac-1 x)
fun-1
nil
nil
此处的输出的第一位是mac-1
,报告其参数。它的参数...是源代码:它恰好是您在函数定义中看到的 。
特别是,当调用宏函数时,x
不是3
,它是x
:这只是源代码,宏的工作是返回另一位源代码,在这种情况下又只是x
。
所以现在我们可以用相同的方式重写mac-2
,您会明白为什么它不可能起作用:
(defmacro mac-2 (&whole form x)
;; broken
(format *debug-io* "~&mac-2 called with ~S~%" form)
(+ x 3))
现在我们可以再次尝试:
> (defun fun-2 (x) (mac-2 x))
fun-2
> (compile *)
mac-2 called with (mac-2 x)
Error: In + of (x 3) arguments should be of type number.
1 (continue) Return a value to use.
2 Supply a new first argument.
3 (abort) Return to top loop level 0.
好的,所以现在应该清楚为什么mac-2
无法正常工作:它被调用了一些源代码(这里是符号x
),并且正在尝试在该位加上3的源代码,除非源代码的那一部分恰好是文字3
,否则该代码将无法工作。 源代码不是数字。
好吧,mac-2
的人生目的是拿一点源代码并再计算一点。特别是,它可以计算一些源代码,在计算时会将一个3加到表达式中。这是一个执行此操作的版本,同时打印将要返回的内容。
(defmacro mac-2 (&whole form x)
(format *debug-io* "~&mac-2 called with ~S~%" form)
(let ((result (list '+ x 3))) ;aka `(+ ,x 3)
(format *debug-io* "~&mac-2 -> ~S~%" result)
result))
这是使用此功能的函数
> (defun fun-3 (x) (mac-2 (sin x)))
fun-3
> (compile *)
mac-2 called with (mac-2 (sin x))
mac-2 -> (+ (sin x) 3)
fun-3
nil
nil
(这是对宏的一种烂用法:它只是作为穷人的内联函数而工作,但这不是重点。)
最后值得考虑的是应该做什么,以及为什么:
(defun fun-4 (x)
(mac-2 (mac-2 (sin x))))
如果您能弄清楚发生宏扩展时将打印什么,以及为什么,那么您可能对Lisp的宏有了很好的了解。
[CL人士请注意:我已经忽略了上面的环境,因为在这个级别上它们并不重要!]
答案 3 :(得分:1)
以上答案正确无误。
没有什么可以阻止您将Mac-2
写为
(defmacro Mac-2 (x)
(if (numberp x)
(+ x 3)
`(+ ,x 3)))
如果是数字参数,将强制在编译时执行计算。通常,当您具有某种纯度的功能时(例如,常量上的sin
),您会想要这种东西。
以这种方式使用宏非常笨拙,如果您确实正在使用应该funcall
才能使用的功能,但是Common-Lisp可以满足您的要求!第三种方法是编译器宏。
首先正常编写函数,然后使用define-compiler-macro
编写本质上是宏的宏,但是与常规宏不同的是,编译器宏可以撑起并拒绝生成扩展,在这种情况下该功能将在正常运行时被调用。
我的实用程序库具有一些用于字符串构建的编译器宏,因为这是Web编程的重要组成部分。