【问题标题】:How to specialize generic function for subclasses of given class如何为给定类的子类专门化泛型函数
【发布时间】:2012-03-03 06:42:44
【问题描述】:

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

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

【问题讨论】:

    标签: common-lisp clos


    【解决方案1】:

    注意,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 之类的内容确保该类已完成,并使用a类原型作为输入(调用CLASS-PROTOTYPE)。这样你就不需要为调度创建类的实例。只需使用原型实例即可。

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

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

    【讨论】:

      【解决方案2】:

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

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

      Common Lisp HyperSpec 词汇表defines“子类”这样写:

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

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

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

      注意括号:“每个类型都是它自己的子类型。”

      它也是 defines 一个“适当的子类型”:

      (属于一个类型)该类型的子类型,与该类型不同类型(即,它的元素是该类型的“正确子集”)。

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

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

      该方法将匹配作为专门器指定类型成员的任何对象。当然,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 结果的唯一好处;不要试图修改它或类似的东西。

      【讨论】:

        猜你喜欢
        • 2019-11-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多