如何使用标准函数调用方法对象

How to call a method object with standard functions

如何将方法对象作为函数来调用?

Closer-mopclos 包都提供了 method-function 来转一个方法对象转化为函数。但是,有没有办法在不包含另一个包的情况下做到这一点?如果不是,哪个包? (使用SBCL),但是如果需要一个包那么判别函数是怎么做的呢?

下面是一个使用find-method获取方法对象的例子。那么问题就是如何调用method-to-be-called.

(defclass a () ((x :accessor x :initform 0)))
(defgeneric inc (i))
(defmethod inc ((i a)) (incf (x i)))
(defvar r (make-instance 'a))

;; ... in a land far far away:    
(defvar method-to-be-called (find-method #'inc '() '(a)))

(funcall method-to-be-called r);; crashes and burns

作为次要问题,文档说辨别函数首先尝试 compute-applicable-methods-by-class 来找到方法对象,然后如果失败,它会使用 compute-applicable-methods。为什么采用这种两层方法?假设 find-method 正在执行这种两层方法是否正确,所以最好使用 find-method ?

-- 附录 -- 在下面的评论中,Rainer Joswig 指出这种查找方法形式依赖于实现:

(find-method #'inc '() '(a))) ; works on sbcl 1.3.1

他说说明符列表应该是 classes 并建议改为:

(find-method #'inc '() (list (find-class 'a))))

所以我想把我的 class 放在那里:

(find-method #'inc '() (list a))  ; crashes and burns

显然 (defclass a ... ) 不会将 a 设置为 class。事实上它没有设置任何东西!

* (defclass a () ((x :accessor x :initform 0)))
#<STANDARD-CLASS COMMON-LISP-USER::A>
* a

... 变量 A 未绑定。

但是,这有效:

* (defvar ca (defclass a () ((x :accessor x :initform 0))))
CA
* (defmethod inc ((i a)) (incf (x i)))
WARNING: Implicitly creating new generic function COMMON-LISP-USER::INC.
#<STANDARD-METHOD COMMON-LISP-USER::INC (A) {1005EE8263}>
enter code here
* (find-method #'inc '() (list ca))   
#<STANDARD-METHOD COMMON-LISP-USER::INC (A) {1005EE8263}>
* 

所以 class 是来自 defclass 的 return 值,而不是提供给 defclass.[=16= 的符号的值]

对于 method-function 的特殊情况,SBCL 的 closer-mop 只需重新导出 sb-pcl 中的现有符号,如 [=22= 中所示]). 这意味着如果您正在使用 SBCL,您可以调用 sb-pcl:method-function(PCL 表示可移植公共循环)。

泛型函数 compute-applicable-methods-by-class 让您知道哪些方法适用于给定的 类。如果您没有可以操作的实际实例,这将很有用。 似乎 compute-applicable-methods-using-classes 允许实现在第二个 return 值为 true 时记住适用的方法。此通用方法不允许您找到适用于 eql 专家的适用方法。

我在这里推测,但返回 compute-applicable-methods 以允许例如 eql-specializers 或因为为 compute-applicable-methods 定义方法稍微容易一些是有意义的. 注意关于一致性的paragraph

The following consistency relationship between compute-applicable-methods-using-classes and compute-applicable-methods must be maintained: for any given generic function and set of arguments, if compute-applicable-methods-using-classes returns a second value of true, the first value must be equal to the value that would be returned by a corresponding call to compute-applicable-methods. The results are undefined if a portable method on either of these generic functions causes this consistency to be violated.

我不认为在任何地方都指定了 find-method-using-classes 通用函数。

(find-method #'inc '() '(a))

以上无效。我们需要 类 的列表,而不是符号列表。

(funcall (method-function (find-method #'inc
                                       '()
                                       (list (find-class 'a))))
         r)

由于函数 method-function 属于 MOP,许多实现都提供了它,并且它在一些实现特定的包中。 CLOSER-MOP 也使其可用。

但通常情况下,如果您已经在尝试提取方法函数,那么您可能以错误的方式使用 CLOS,或者您真的知道自己在做什么...

How does one call a method object as a function?

诚实的问题:你为什么要这样做?您是否首先指定了该方法的功能是如何构建的?

即使使用 closer-mop,我相信由 closer-mop:method-function 编辑的函数 return 至多在其 lambda 列表方面与 closer-mop:make-method-lambda 一致,所以也许你可以使用一个包来知道你可以移植什么。

方法的函数不必是与泛型函数具有相同 lambda 列表的函数,而且通常不是因为 next-method-pcall-next-method。某些实现可能对下一个方法列表使用动态绑定,因此这些实现可能具有与通用函数一致的方法 lambda 列表。一般而言,不要指望它。

我相信 SBCL 不是这些实现之一,下一个方法列表传递给方法的函数以支持 next-method-pcall-next-method

Why do this two layer approach?

因为它允许在可能的情况下基于 classes 列表进行记忆(或缓存)。如果使用相同的classes的参数再次调用泛型函数,并且泛型函数没有被更新(参见MOP中的"Dependent Maintenance Protocol"),它可以重用最后的结果而不需要进一步处理,因为例如,通过将结果保存在哈希 table 中,其中键是 classes.

的列表

但是,如果 compute-applicable-methods-using-classes return 是一个错误的第二个值,则使用 compute-applicable-methods。原因是找不到单独使用 classes 的方法,这意味着某些方法具有非 class 专用程序。

这不同于说没有适用的方法,例如,如果所有方法都专门针对 classes 并且没有适用的方法,compute-applicable-methods-using-classes 应该 return空列表和真正的第二个值。调用 compute-applicable-methods 没有意义,它不会(或者更确切地说,如果实施得当,它不应该)进一步找到任何东西。

当使用 compute-applicable-methods 时仍然可以执行记忆,但记忆不再像使用 classes 列表作为散列中的键那样简单table。也许您可以使用树结构,在其中尝试为每个参数(实例,然后是 class)依次查找方法,直到树节点与整个可特殊化参数列表相匹配。

使用非标准专业化程序,您必须更改每个节点的搜索顺序。除非此类专家的优先级严格来说不在 eql 和 class 之前、之间或之后,否则您将进入未知领域。

实际上,您必须更改 compute-applicable-methods-using-classes 以识别非标准特化程序和 return false 及早,并且您必须更改 compute-applicable-methods 以处理这些特化程序, 无论如何,所以也许你会很好地了解如何记住 compute-applicable-methods 的结果。

Is it correct to assume the find-method is doing this two layer approach, so it is better to use find-method ?

不,find-method的目的是寻找特定方法,而不是适用方法。它根本不使用 compute-applicable-methods-using-classescompute-applicable-methods。事实上,它永远不能使用后者,因为它需要实际参数而不是特化参数。