我如何专门化泛型函数来获取指定给定类的子类的符号。 例如:
(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可以进行此类调度吗?如果是,我应该写什么而不是“ 的子类”?
答案 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
的结果唯一有用的东西;不要试图修改它或类似的东西。