gpt4 book ai didi

macros - Lisp 宏找不到适用的功能

转载 作者:行者123 更新时间:2023-12-04 22:34:17 26 4
gpt4 key购买 nike

给定宏:


(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-1p 绑定(bind)到的任何地方,这是一个符号。然后这会失败,结果宏展开失败。

好吧,你可以用一种看起来很明显的方式“修复”这个问题:

(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)))))

所以这个(实际上它的以前版本也是如此)终于可以工作了。

等等,等等。我们所做的是将这个东西变成:

  • 将其参数的值绑定(bind)到一个变量;
  • 并使用该绑定(bind)评估一些代码。

有一个名称可以用来表示执行此操作的东西,这个名称就是函数

(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)))

所以这里的总结是:

将宏用于其用途,即转换源代码。如果您不想这样做,请使用函数。如果您确定调用函数的行为占用了大量时间,则考虑将它们声明为内联以避免这种情况。

关于macros - Lisp 宏找不到适用的功能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65505001/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com