【问题标题】:Why does calling `make-instance` in `let` work differently?为什么在 `let` 中调用 `make-instance` 的工作方式不同?
【发布时间】:2019-10-30 05:50:52
【问题描述】:

我正在探索 Common Lisp 语法的一些可能性,我想在 make-instance 上创建一个 :around 方法以在某些情况下返回一些任意值。为简单起见,当我没有传递所需的参数时,让它成为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

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

【问题讨论】:

    标签: common-lisp sbcl let clos


    【解决方案1】:

    您的程序不合格,因此您观察到的任何结果都可能从一种实现更改为另一种实现,或者从一个实现版本更改为另一种实现。

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

    为标准化的泛型函数定义一个方法,该方法适用于所有参数都是标准化类的直接实例的情况。

    元对象协议强加restrictions on portable programs

    可移植程序在指定泛型上定义的任何方法 函数必须至少有一个既不是 指定的类也不是关联值为 指定类的实例。

    在您的情况下,标准化的通用函数是 MAKE-INSTANCE,而 eql 专门工具的关联值为 foosymbol 类的一个实例。

    (eql (find-class 'foo))方法中,关联值是standard-class的直接实例,所以也不符合;您应该使用自定义元类在 make-instance 上定义新方法。

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

    可移植程序可以定义扩展指定方法的方法 除非指定方法的描述明确禁止 这。除非有相反的具体声明,否则这些 扩展方法必须返回调用返回的任何值 调用下一个方法。

    超规范规定make-instance 必须返回给定 的新实例,其中instance 被定义为直接实例 em> 或 间接实例direct instance 的词汇表条目有一个例句,说 make-instance 总是返回一个类的 direct 实例(词汇表条目是规范的(这里是一个例句,所以可能有解释的余地​​))。但是,返回 NIL 是不合格的。

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

    【讨论】:

      【解决方案2】:

      看起来像是对MAKE-INSTANCE 和 SBCL (-> 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 专家或提交错误报告可能很有用...

      【讨论】:

      • 谢谢,实际上(class (eql (find-class 'foo))) 足以让它工作,但很高兴知道你在这里使用的其他东西。
      猜你喜欢
      • 2015-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-24
      • 2020-02-18
      相关资源
      最近更新 更多