为什么在 `let` 中调用 `make-instance` 的方式不同?

Why does calling `make-instance` in `let` work differently?

我正在探索 Common Lisp 语法的一些可能性,我想在 make-instance 上创建一个 :around 方法到 return 在某些情况下的某个任意值。为了简单起见,当我没有传递所需的参数时,让它成为 nil。它有效,但在调用 let:

时无效
(defclass foo ()
  ((bar :initarg := :initform '())))

(defmethod make-instance :around ((type (eql 'foo)) &key =)
  (if (not =) nil (call-next-method)))

(print (make-instance 'foo))    ;; => NIL

(print (let ((x (make-instance 'foo))) x)) ;; => #<FOO {10037EEDF3}> 

谁能解释一下这种情况?为什么? SBCL 是试图变得聪明还是实际上是一件标准的事情?我知道我可以通过使用 apply:

来解决它
(print (let ((x (apply #'make-instance (list 'foo)))) x)) ;; => NIL

但我不想依赖这种解决方法。实际上,我可以为此使用常规函数,没关系,但我想了解为什么它会这样工作,以及是否可以禁用此行为。

看起来像是对 MAKE-INSTANCE 和 SBCL 中常量 class 名称的优化尝试之一 (-> CTOR)...

这似乎有效:

(defmethod make-instance :around ((class (eql (find-class 'foo)))
                                  &rest initargs
                                  &key =
                                  &allow-other-keys)
  (declare (ignorable initargs))
  (if (not =) nil (call-next-method)))

但询问 SBCL 专家或提交错误报告可能很有用...

您的程序是 non-conforming,因此您观察到的任何结果都可能从一个实现更改为另一个实现,或者从一个实现版本更改为另一个版本。

参见 Constraints on the COMMON-LISP Package for Conforming Programs,特别是第 19 条:

Defining a method for a standardized generic function which is applicable when all of the arguments are direct instances of standardized classes.

meta-object 协议强加 restrictions on portable programs:

Any method defined by a portable program on a specified generic function must have at least one specializer that is neither a specified class nor an eql specializer whose associated value is an instance of a specified class.

在你的例子中,标准化的泛型函数是 MAKE-INSTANCE,eql specializer 的关联值是 foosymbol class.

的一个实例

(eql (find-class 'foo))方法中,关联值是standard-class的直接实例,所以也是non-conforming;您应该使用自定义元 class 在 make-instance.

上定义新方法

此外,:around方法returning nil是另一个问题:

Portable programs may define methods that extend specified methods unless the description of the specified method explicitly prohibits this. Unless there is a specific statement to the contrary, these extending methods must return whatever value was returned by the call to call-next-method.

hyperspec 说 make-instance 必须 return 给定 class[=48= 的新 实例 ],其中 instance is defined as either a direct instance or an indirect instance. The glossary entry for direct instance 有一个例句说 make-instance 总是 return a direct a class 的实例(词汇表条目是规范的(这里是例句,所以可能有解释的余地​​))。然而,返回 NIL 是 non-conforming.

感谢 SBCL 开发人员的宝贵时间;参见 https://bugs.launchpad.net/sbcl/+bug/1835306