Common Lisp 宏宏扩展时间参数类型
Common Lisp macro macroexpansion time argument typing
在下面的函数中,我用 mac-1 替换了 macro,这只是 returns 它有效的参数值,但是 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)
Execution of a form compiled with errors.
Form:
(MAC-2 X)
Compile-time error:
during macroexpansion of (MAC-2 X). Use BREAK-ON-SIGNALS to intercept.
The value
X
is not of type
NUMBER
when binding SB-KERNEL::X
(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
尚不存在。
重命名事物:
(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。
记住:宏接受任意表达式并创建新的表达式。在宏扩展时,应该看到源代码,而不是值。
Means x
passed from fun-2
to mac-2
is here the source code (the data)? And the macro is expanded with this data (the symbol x
) as soon as fun-2
is evaluated, and the Expansion is put into the body of fun-2
? Step by step I am beginning to begin to understand that I don't understand macros yet!
差不多:宏也可以在函数被求值之前展开。例如,当您编译一个函数时,宏会被展开 --> 显然,如果宏需要运行时值,这是不可能的。
宏完全取代了更多的动态机制,因为它们可以编译为高效代码,而在运行时不需要宏扩展。
将 x
传递给宏的函数的心智模型并没有多大帮助。
作为第一个基本想法更像这样思考:宏形式将被宏转换为一些新表达式 - 在编译期间在编译实现中,在评估期间在解释版本中。
mac-2
是宏的名称
(mac-2 x)
是一个 宏形式 ,因为它是复合形式,而 mac-2
是宏的名称。
一般所有要计算的表达式都称为形式:函数形式、宏形式、特殊形式、lambda形式、符号和自计算对象。
Can you explain what the type declaration in your macro is for?
只是为了阐明您可以期望的参数类型。
不要在代码中使用它,而是使用 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
)
通常在可移植代码中,当您的宏期望某些参数属于特定类型或形状时,可能需要添加 check-type
调用以明确这一点。假设我们要编写一个宏来定义一个 planet 并且我们需要一个名称,它应该是一个符号或一个字符串:
(defmacro defplanet (name coordinates)
(check-type name (or string symbol)
"The name of the planet must be a symbol or a string.")
`(intern-planet ,name (check-coordinates ,coordinates)))
以上将在宏扩展时检查 'name' 的值的类型。
了解 Lisp 中的宏的关键是它们指定了源到源的转换:一个宏得到了一些 Lisp 源代码,return另一个一些 Lisp 源代码,其中源代码(在 Common Lisp 和其他相关 Lisp 中)表示为 s 表达式。这种扩展发生在代码被评估之前——从概念上讲,它发生在准备评估代码的过程中,通常是编译过程的一部分。
特别是像
这样的表达式
(defmacro foo (x)
...)
定义了一个非常普通的 Lisp 函数,它的参数是一段源代码(一种形式)并且需要 return 另一段源代码。
当用 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
:它只是一点源代码,宏的工作是return 另一位源代码,在本例中又是 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 添加到表达式中。这是一个执行此操作的版本,同时将其打印到 return.
(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 人员请注意:我忽略了上面的环境,因为它们在这个级别上并不重要!]
以上答案正确,但不完整。
绝对没有什么可以阻止您将 Mac-2
写成
(defmacro Mac-2 (x)
(if (numberp x)
(+ x 3)
`(+ ,x 3)))
如果它是数字参数,这将强制在编译时执行计算。通常当你有一个具有某种形式纯度的功能时你想要这种东西(例如,sin
常量)。
以这种方式使用宏是笨拙的,如果你真的正在使用应该 funcall
-able 的东西,但 Common-Lisp 已经涵盖了你!第三种方式是compiler-macros.
首先正常编写函数,然后使用 define-compiler-macro
编写本质上是宏的内容,但与常规宏不同的是,编译器宏可以平底船并拒绝生成扩展,在在这种情况下,该函数将在运行时正常调用。
我的实用程序库有一些用于构建字符串的编译器宏,因为这是网络编程的重要组成部分。
在下面的函数中,我用 mac-1 替换了 macro,这只是 returns 它有效的参数值,但是 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)
Execution of a form compiled with errors. Form: (MAC-2 X) Compile-time error: during macroexpansion of (MAC-2 X). Use BREAK-ON-SIGNALS to intercept.
The value X is not of type NUMBER when binding SB-KERNEL::X
(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
尚不存在。
重命名事物:
(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。
记住:宏接受任意表达式并创建新的表达式。在宏扩展时,应该看到源代码,而不是值。
Means
x
passed fromfun-2
tomac-2
is here the source code (the data)? And the macro is expanded with this data (the symbolx
) as soon asfun-2
is evaluated, and the Expansion is put into the body offun-2
? Step by step I am beginning to begin to understand that I don't understand macros yet!
差不多:宏也可以在函数被求值之前展开。例如,当您编译一个函数时,宏会被展开 --> 显然,如果宏需要运行时值,这是不可能的。
宏完全取代了更多的动态机制,因为它们可以编译为高效代码,而在运行时不需要宏扩展。
将 x
传递给宏的函数的心智模型并没有多大帮助。
作为第一个基本想法更像这样思考:宏形式将被宏转换为一些新表达式 - 在编译期间在编译实现中,在评估期间在解释版本中。
mac-2
是宏的名称(mac-2 x)
是一个 宏形式 ,因为它是复合形式,而mac-2
是宏的名称。
一般所有要计算的表达式都称为形式:函数形式、宏形式、特殊形式、lambda形式、符号和自计算对象。
Can you explain what the type declaration in your macro is for?
只是为了阐明您可以期望的参数类型。
不要在代码中使用它,而是使用 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
)
通常在可移植代码中,当您的宏期望某些参数属于特定类型或形状时,可能需要添加 check-type
调用以明确这一点。假设我们要编写一个宏来定义一个 planet 并且我们需要一个名称,它应该是一个符号或一个字符串:
(defmacro defplanet (name coordinates)
(check-type name (or string symbol)
"The name of the planet must be a symbol or a string.")
`(intern-planet ,name (check-coordinates ,coordinates)))
以上将在宏扩展时检查 'name' 的值的类型。
了解 Lisp 中的宏的关键是它们指定了源到源的转换:一个宏得到了一些 Lisp 源代码,return另一个一些 Lisp 源代码,其中源代码(在 Common Lisp 和其他相关 Lisp 中)表示为 s 表达式。这种扩展发生在代码被评估之前——从概念上讲,它发生在准备评估代码的过程中,通常是编译过程的一部分。
特别是像
这样的表达式(defmacro foo (x)
...)
定义了一个非常普通的 Lisp 函数,它的参数是一段源代码(一种形式)并且需要 return 另一段源代码。
当用 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
:它只是一点源代码,宏的工作是return 另一位源代码,在本例中又是 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 添加到表达式中。这是一个执行此操作的版本,同时将其打印到 return.
(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 人员请注意:我忽略了上面的环境,因为它们在这个级别上并不重要!]
以上答案正确,但不完整。
绝对没有什么可以阻止您将 Mac-2
写成
(defmacro Mac-2 (x)
(if (numberp x)
(+ x 3)
`(+ ,x 3)))
如果它是数字参数,这将强制在编译时执行计算。通常当你有一个具有某种形式纯度的功能时你想要这种东西(例如,sin
常量)。
以这种方式使用宏是笨拙的,如果你真的正在使用应该 funcall
-able 的东西,但 Common-Lisp 已经涵盖了你!第三种方式是compiler-macros.
首先正常编写函数,然后使用 define-compiler-macro
编写本质上是宏的内容,但与常规宏不同的是,编译器宏可以平底船并拒绝生成扩展,在在这种情况下,该函数将在运行时正常调用。
我的实用程序库有一些用于构建字符串的编译器宏,因为这是网络编程的重要组成部分。