常见的Lisp宏宏扩展时间参数键入

时间:2020-01-02 09:04:10

标签: macros lisp common-lisp

在下面的函数中,我用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

4 个答案:

答案 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)并不可行,因为那时宏获得xx尚不存在。

答案 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编程的重要组成部分。

  1. String concatenation
  2. String joining
相关问题