如何为给定类的子类专门化泛型函数

时间:2012-03-03 06:42:44

标签: common-lisp clos

我如何专门化泛型函数来获取指定给定类的子类的符号。 例如:

(defclass a () ())
(defclass b (a) ())
(defclass c (b) ())
(defclass d () ())

(defgeneric fun (param))
(defmethod fun ((param (<subclass of> a)))
  (format t "~a is a subclass of A~%" param))

(fun 'c) ;-> "C is a subclass of A"
(fun 'd) ;-> Error: not found method for generic function call (fun 'd)

CLOS可以进行此类调度吗?如果是,我应该写什么而不是“ 的子类”?

2 个答案:

答案 0 :(得分:5)

请注意,Common Lisp具有函数SUBTYPEP

CL-USER 15 > (subtypep 'd 'a)
NIL
T

CL-USER 16 > (subtypep 'c 'a)
T
T

有关两个返回值的含义,请参阅SUBTYPEP的文档(首先说明它是否为子类型)。类也是类型。

这意味着您的功能就是:

(defun fun (class-name)
  (if (subtypep class-name 'a)
      (format t "~a is a subclass of A~%" class-name)
    (error "wtf")))

请记住:方法中的继承适用于类继承。这意味着使用您拥有的继承来传递某个类的实例:

(defmethod fun ((param a))
  (format t "~a is a subclass of A~%" (class-name (class-of param))))

上面是一个类A的实例。

称之为:

CL-USER 29 > (fun (make-instance 'a))
A is a subclass of A
NIL

CL-USER 30 > (fun (make-instance 'c))
C is a subclass of A
NIL

CL-USER 31 > (fun (make-instance 'd))

Error: No applicable methods for #<STANDARD-GENERIC-FUNCTION FUN 418001813C>
with args (#<D 40200011E3>)
  1 (continue) Call #<STANDARD-GENERIC-FUNCTION FUN 418001813C> again
  2 (abort) Return to level 0.
  3 Return to top loop level 0.

Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.

CL-USER 32 : 1 > 

有一种方法可以简化^ h ^ h ^ h ^ h ^ h ^ h ^ h ^ h使调用更容易:您可以确保使用类似CLOS:FINALIZE-INHERITANCE和使用类原型作为输入(调用CLASS-PROTOTYPE)。这样您就不需要为调度创建类的实例。人们只会使用原型实例。

另一种丑陋的版本是硬编码值:

(defmethod fun0 ((param (eql 'b)))
  T)

(defmethod fun0 ((param (eql 'c)))
  T)

答案 1 :(得分:2)

仅使用CLOS调度,您将无法轻松执行此确切的任务。

在继续之前,我认为一些关于术语的简短说明很重要。

Common Lisp HyperSpec词汇表defines“子类”以这种方式:

  

一个继承自另一个类的类,称为超类。 (没有   class是其自身的子类。)

这个定义虽然直观,但对我来说似乎很奇怪,因为我希望它是“适当的子类”的定义。但是,所有类都是类型,并且defines“子类型”为:

  

一种类型,其成员资格与另一种类型的成员资格的相同或适当的子集,称为超类型。 (每种类型都是其自身的子类型。)

请注意括号:“每种类型都是其自身的子类型。”

它还defines一个“正确的子类型”:

  

(类型)类型的子类型与类型的类型不同(即,它的元素是类型的“适当子集”)。

因此,在您的示例中,B和C是A的子类,也是子类型。另一方面,B,C,和A 是A的子类型。

在defmethod中放置的东西是"parameter specializer name"。它可以是符号,类(有点难以键入),也可以是以eql开头的列表。如果提供符号,则它指定由该符号命名的类(当然,这是一种类型)。 eql列表指定一个由对象组成的类型,这些对象是列表中事物的eql。

该方法将匹配任何对象,该对象是specializer指定的类型的成员。当然,X子类的成员也是X的成员。

所以你的第一个问题是你将符号对象传递给你的方法;每个符号都是SYMBOL类型。在这方面,碰巧命名一个类的符号没有什么不同;它与类的唯一关系是它是类的名称,它不是子类型关系。

有类对象(由find-class返回),但它们并不比这里的方法特化符号好,因为类对象的类型通常与其子类的类对象的类型相同。

因此,您将继续使用实例或阅读AMOP来学习如何创建自己类型的泛型函数。

一旦有了实例,就可以编写如下方法:

(defmethod fun ((param a))
  (if (eq (type-of param) 'a)
    (call-next-method)
    (format t "~a is a subclass of A~%" (type-of param))))

如果您有一种简单的方法来检索类的实例,您可以编写这个包装器:

(defmethod fun ((param symbol))
  (fun (retrieve-instance param)))

然后,您将能够将符号传递给乐趣并获得您想要的结果。

如果您想使用AMOP功能(标准未指定但可广泛使用,请参阅Closer Project),您可以像这样定义retrieve-instance

(defun retrieve-instance (name)
  (let ((class (find-class name)))
    (unless (class-finalized-p class)
      (finalize-inheritance class))
    (class-prototype class)))

请注意,方法调度只是class-prototype的结果唯一有用的东西;不要试图修改它或类似的东西。