3

我如何专门化一个通用函数来获取指定给定类的子类的符号。例如:

(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可以进行这种调度吗?如果是,我应该写什么而不是“子类”?

4

2 回答 2

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

以上采用 class 的一个实例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)
于 2012-03-03T08:02:49.100 回答
2

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

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

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

从另一个类继承的类,称为超类。(没有类是它自己的子类。)

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

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

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

它还定义了一个“正确的子类型”:

(of a type) 类型的子类型,与类型不同的类型(即,它的元素是类型的“正确子集”)。

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

放在 defmethod 中的东西是“参数专业名称”。它可以是一个符号、一个类(有点难打)或一个以 eql 开头的列表。如果您提供一个符号,它指定由该符号命名的类(当然,它是一种类型)。一个 eql 列表指定一个类型,该类型由与列表中的事物 eql 的对象组成。

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

所以你的第一个问题是你将符号对象传递给你的方法;每个符号都是 type 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;不要试图修改它或类似的东西。

于 2012-03-03T19:54:12.600 回答