首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >常用Lisp宏宏展开时间参数类型

常用Lisp宏宏展开时间参数类型
EN

Stack Overflow用户
提问于 2020-01-02 09:04:10
回答 4查看 325关注 0票数 0

在下面的函数中,我用mac-1替换了宏,它只返回它工作的参数的值,但是mac-2抛出一个类型错误。有人能解释一下发生了什么并导致这个错误吗?为什么x是从x传递给mac-2的文字符号,而不是整数呢?

代码语言:javascript
复制
(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-2X)编译时错误:宏扩展期间(MAC-2X).用信号中断来拦截。 当绑定SB-核::x时,值X不是类型数。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2020-01-02 10:33:25

重命名这个东西:

代码语言:javascript
复制
(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添加到符号中。

记住:宏接受任意表达式并创建新表达式。在宏展开时,我们应该看到源代码,而不是值。

意思是xfun-2传递到mac-2这里是源代码(数据)吗?在对x进行计算后,用该数据(符号fun-2 )展开宏,并将展开放入fun-2的体中?一步一步地,我开始明白,我还不懂宏!

几乎:宏也可以在评估函数之前展开。例如,当您编译一个函数时,宏就会展开-显然,如果宏需要运行时值,这是不可能的。

宏完全取代了更多的动态机制,因为它们支持将代码编译成高效的代码,在运行时不需要宏展开。

将一个x传递给宏的函数的心理模型并没有多大帮助。

更像这样,作为第一个基本概念:宏将被宏转换为一些新的表达式--编译时的编译实现和计算期间的解释版本。

  • mac-2是宏的名称
  • (mac-2 x)是宏表单,因为它是复合表单,而mac-2是宏的名称。

通常,所有要计算的表达式都称为形式:函数形式、宏形式、特殊形式、lambda形式、符号和自评估对象。

能否解释宏中的类型声明是为了什么?

只是为了明确什么是参数类型,您可以期望。不要在代码中使用它,而是使用check-type (见下文)。

代码语言:javascript
复制
(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调用来澄清这一点。假设我们想要编写一个宏来定义一个行星,我们需要一个名称,它应该是一个符号或字符串:

代码语言:javascript
复制
(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”值的类型。

票数 7
EN

Stack Overflow用户

发布于 2020-01-02 09:56:43

(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-2x存在时进行计算。例如:

代码语言:javascript
复制
(defmacro mac-2 (x)
  `(+ ,x 3))

创建fun2时,它将展开宏,不会立即尝试进行计算,但函数存储为:

代码语言:javascript
复制
(defun fun-2 (x) (+ x 3))

然后调用fun-2时,x就会存在,(+ x 3)就有意义了。在创建函数时执行(+ x 3)不会,因为那时宏获得x,而x还不存在。

票数 6
EN

Stack Overflow用户

发布于 2020-01-02 11:21:28

对于Lisp中的宏,需要了解的关键是它们将源代码指定为源代码转换:宏获取一些Lisp源代码,并返回另一部分Lisp源代码,其中源代码被表示为s-表达式(在公共Lisp和其他相关的Lisps中)。这种扩展发生在代码被评估之前--从概念上讲,它是准备代码进行评估的过程的一部分,通常是编译代码过程的一部分。

特别是,像这样的表达式

代码语言:javascript
复制
(defmacro foo (x)
  ...)

定义一个非常普通的Lisp函数,其参数是一个源代码块(一个表单),需要返回另一个源代码块。

在用defmacro定义宏时,函数获取的表单通常对您来说是不可见的,因为defmacro根据您给出的参数列表将它分开,并且只提供您所关心的部分。但是宏参数列表中有一个特殊的选项,&whole,它可以让您看到整个表单。

因此,我们可以定义宏的一个版本,该版本显示它们的调用方式,以及源代码块是什么:

代码语言:javascript
复制
(defmacro mac-1 (&whole form x)
  (format *debug-io* "~&mac-1 called with ~S~%" form)
  x)

这与您的mac-1相同,但它将在宏展开时打印它所调用的表单。

现在我们可以定义一个使用以下内容的函数:

代码语言:javascript
复制
(defun fun-1 (x)
  (mac-1 x))

当与mac-1对应的函数被调用时&它被调用的次数取决于实现。在只有编译器的实现中,在计算表单时,可能至少会调用一次。在带有解释器的实现中,它可能稍后会被调用。在我使用的实现(LispWorks)中,调用它的简单方法是显式编译函数:

代码语言:javascript
复制
 > (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,您可以看到为什么它不能工作:

代码语言:javascript
复制
(defmacro mac-2 (&whole form x)
  ;; broken
  (format *debug-io* "~&mac-2 called with ~S~%" form)
  (+ x 3))

现在我们可以再试一次:

代码语言:javascript
复制
> (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添加到表达式中。下面是一个这样的版本,同时打印它将要返回的内容。

代码语言:javascript
复制
(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))

下面是一个函数,它使用这个

代码语言:javascript
复制
> (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

(这将是对宏的错误使用:它只是充当穷人的内联函数,但这不是重点。)

最后,值得考虑的是这样的事情应该做些什么,以及为什么:

代码语言:javascript
复制
(defun fun-4 (x)
  (mac-2 (mac-2 (sin x))))

如果您能够计算出发生宏扩展时会打印什么,以及为什么您可能很好地掌握了Lisp中的宏。

请注意,CL人员:我忽略了上面的环境,因为他们在这个级别上并不重要!

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59560702

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档