如何解决Lisp 宏找不到适用的函数
给定宏:
(defclass sample-class ()
((slot-1 :accessor slot-1
:initform "sample slot")))
(defvar *sample-instance*(make-instance 'sample-class))
(defmacro sample-macro (p)
`(if (typep,p 'sample-class)
(progn
(print "evaluated")
(print,(slot-1 p)))))
(sample-macro *sample-instance*)
我很困惑为什么这是错误输出
Execution of a form compiled with errors.
Form:
(SAMPLE-MACRO *SAMPLE-INSTANCE*)
Compile-time error:
(during macroexpansion of (SAMPLE-MACRO *SAMPLE-INSTANCE*))
There is no applicable method for the generic function
#<STANDARD-GENERIC-FUNCTION COMMON-LISP-USER::SLOT-1 (1)>
when called with arguments
(*SAMPLE-INSTANCE*).
See also:
The ANSI Standard,Section 7.6.6
[Condition of type SB-INT:COMPILED-PROGRAM-ERROR]
宏不应该在过程中展开和评估s-form吗?为什么读者找不到泛型函数 slot-1
?
解决方法
我认为您对宏的作用感到困惑。宏是源代码的转换。所以考虑一下当系统尝试扩展宏表 (sample-macro *sample-instance*)
时会发生什么。在宏扩展时,p
是符号 *sample-instance*
:表示一小段源代码。
现在,查看宏主体中的反引号形式:其中有 ,(slot-1 p)
:这将尝试在 slot-1
绑定到的任何对象上调用 p
,其中是一个符号。然后失败,结果宏展开失败。
好吧,你可以用一种看起来很明显的方式“修复”这个问题:
(defmacro sample-macro (p)
`(if (typep,p 'sample-class)
(progn
(print "evaluated")
(print (slot-1,p)))))
这似乎有效。使用宏扩展跟踪器:
(sample-macro *sample-instance*)
-> (if (typep *sample-instance* 'sample-class)
(progn (print "evaluated") (print (slot-1 *sample-instance*))))
如果您使用宏,它将“起作用”。除了它根本不起作用:考虑这种形式:(sample-macro (make-instance 'sample-class))
:好吧,让我们看看使用宏跟踪器:
(sample-macro (make-instance 'sample-class))
-> (if (typep (make-instance 'sample-class) 'sample-class)
(progn
(print "evaluated")
(print (slot-1 (make-instance 'sample-class)))))
哦,亲爱的。
所以我们可以通过像这样重写宏来解决这个问题:
(defmacro sample-macro (p)
`(let ((it,p))
(if (typep it 'sample-class)
(progn
(print "evaluated")
(print (slot-1 it)))
现在
(sample-macro (make-instance 'sample-class))
-> (let ((it (make-instance 'sample-class)))
(if (typep it 'sample-class)
(progn (print "evaluated") (print (slot-1 it)))))
哪个更好。在这种情况下它甚至是安全的,但在大多数情况下,我们需要对我称之为 it
的东西使用 gensym:
(defmacro sample-macro (p)
(let ((itn (make-symbol "IT"))) ;not needed for this macro
`(let ((,itn,p))
(if (typep,itn 'sample-class)
(progn
(print "evaluated")
(print (slot-1,itn)))))))
现在:
(sample-macro (make-instance 'sample-class))
-> (let ((#:it (make-instance 'sample-class)))
(if (typep #:it 'sample-class)
(progn (print "evaluated") (print (slot-1 #:it)))))
所以这个(实际上也是它以前的版本)终于可以工作了。
等等,等等。我们所做的就是把这件事变成这样:
- 将其参数的值绑定到一个变量;
- 并使用该绑定评估一些代码。
有一个名字可以用来做这件事,这个名字是函数。
(defun not-sample-macro-any-more (it)
(if (typep it 'sample-class)
(progn
(print "evaluated")
(print (slot-1 it)))))
这完成了 sample-macro
的工作版本所做的一切,但没有所有不必要的复杂性。
嗯,它不会做一件事:它不会内联扩展,也许这意味着它可能会慢一点。
好吧,在以煤为燃料的 Lisp 时代,这是一个真正的问题。燃煤 Lisp 系统具有由木屑和锯末制成的原始编译器,并在确实非常慢的计算机上运行。所以人们会写一些语义上应该是宏的东西,这样木头编译器就会内联代码。有时这甚至是值得的。
但现在我们有了先进的编译器(可能仍然主要由木屑和锯末制成),我们可以说出我们的实际意思:
(declaim (inline not-sample-macro-any-more))
(defun not-sample-macro-any-more (it)
(if (typep it 'sample-class)
(progn
(print "evaluated")
(print (slot-1 it)))))
现在您可以合理地保证 not-sample-macro-any-more
将被内联编译。
在这种情况下甚至更好(但代价是几乎可以肯定没有内联内容):
(defgeneric not-even-slightly-sample-macro (it)
(:method (it)
(declare (ignore it))
nil))
(defmethod not-even-slightly-sample-macro ((it sample-class))
(print "evaluated")
(print (slot-1 it)))
所以这里的总结是:
将宏用于它们的用途,即转换源代码。如果您不想这样做,请使用函数。如果您确定调用函数的行为会占用大量时间,那么请考虑将它们声明为内联以避免这种情况。
,其他答案解释说,宏执行是关于转换宏扩展时可用的源代码和值。
让我们也试着理解错误信息。我们需要从字面上理解:
Execution of a form compiled with errors.
上面说的是编译。
Form:
(SAMPLE-MACRO *SAMPLE-INSTANCE*)
以上是要编译的源代码。
Compile-time error:
(during macroexpansion of (SAMPLE-MACRO *SAMPLE-INSTANCE*))
再次:编译,现在特别是在宏扩展期间。
There is no applicable method for the generic function
#<STANDARD-GENERIC-FUNCTION COMMON-LISP-USER::SLOT-1 (1)>
when called with arguments
(*SAMPLE-INSTANCE*).
现在上面是有趣的部分:没有适用于泛型函数 SLOT-1
和参数 *SAMPLE-INSTANCE*
的方法。
什么是*SAMPLE-INSTANCE*
? 这是一个符号。在您的代码中有一个方法,但它用于类 sample-class
的实例。但是没有符号的方法。所以这行不通:
(setf p '*sample-instance*)
(slot-1 p)
这基本上就是你的代码所做的。您希望使用运行时值,但您在编译时得到的只是一个源符号...
显示带有源代码元素的编译时错误的编译器错误消息表明存在运行时和宏扩展时间计算的混淆。
See also:
The ANSI Standard,Section 7.6.6
[Condition of type SB-INT:COMPILED-PROGRAM-ERROR]
,
要了解宏的作用,让我们使用 macroexpand
。
(macroexpand-1 '(sample-macro *sample-instance*))
=>
There is no applicable method for the generic function
#<STANDARD-GENERIC-FUNCTION COMMON-LISP-USER::SLOT-1 (1)>
when called with arguments
(*SAMPLE-INSTANCE*).
[Condition of type SB-PCL::NO-APPLICABLE-METHOD-ERROR]
糟糕,同样的错误信息。我将简化宏并删除 slot-1
周围的评估。
(defmacro sample-macro (p)
`(if (typep,p 'sample-class)
(progn
(print "evaluated")
(print (slot-1 p)))))
(macroexpand-1 '(sample-macro *sample-instance*))
=>
(IF (TYPEP *SAMPLE-INSTANCE* 'SAMPLE-CLASS)
(PROGN (PRINT "evaluated") (PRINT (SLOT-1 P))))
代码看起来不错,直到变量 P
。那么它可以简单地与,p
一起使用吗?无需写 ,(slot-1 p)
,因为 slot-1
在这里正确。
(defmacro sample-macro (p)
`(if (typep,p 'sample-class)
(progn
(print "evaluated")
(print (slot-1,p)))))
(macroexpand-1 '(sample-macro *sample-instance*))
=>
(IF (TYPEP *SAMPLE-INSTANCE* 'SAMPLE-CLASS)
(PROGN (PRINT "evaluated") (PRINT (SLOT-1 *SAMPLE-INSTANCE*))))
代码看起来是正确的。
(sample-macro *sample-instance*)
"evaluated"
"sample slot"
它有效。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。