Pergunta

How can i specialize a generic function to take symbols designating subclasses of given class. For example:

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

Is such dispatching possible with CLOS? And if it is, what should I write instead of "subclass of"?

Foi útil?

Solução

You won't be able to easily perform this exact task using only CLOS dispatching.

Before I continue, I think some brief notes on terminology is important.

The Common Lisp HyperSpec glossary defines "subclass" in this way:

a class that inherits from another class, called a superclass. (No class is a subclass of itself.)

This definition, while intuitive, seems odd to me as I'd expect that to be the definition of a "proper subclass". However, all classes are types, and it defines "subtype" as:

a type whose membership is the same as or a proper subset of the membership of another type, called a supertype. (Every type is a subtype of itself.)

Note the parenthetical: "Every type is a subtype of itself."

It also defines a "proper subtype":

(of a type) a subtype of the type which is not the same type as the type (i.e., its elements are a ``proper subset'' of the type).

So, in your example, B and C are subclasses of A, and also subtypes. On the other hand B, C, and A are subtypes of A.

The thing one puts in defmethod is a "parameter specializer name". It can be a symbol, a class (which is a little hard to type), or a list starting with eql. If you provide a symbol, it specifies the class named by that symbol (which is, of course, a type). An eql list specifies a type consisting of objects which are eql to the thing in the list.

The method will match any object which is a member of the type the specializer specifies. And of course, a member of a subtype of X is also a member of X.

So your first problem is that you are passing symbol objects to your method; every symbol is of type SYMBOL. A symbol that happens to name a class is no different in this respect; it's only relationship to the class is that it is the class's name, which is not a subtype relation.

There are class objects (returned by find-class), but they're no better than symbols for method specialization here because the type of a class object is usually the same as the type of its subclasses' class objects.

So, you're left using instances or reading AMOP to learn how to create your own types of generic functions.

Once you have an instance, you can write the method like this:

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

If you have an easy way to retrieve instances of your classes, you could write this wrapper:

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

Then you'll be able to pass symbols to fun and get the results you want.

If you want to use AMOP functions (which were not specified by the standard but are widely available, see Closer Project), you can define retrieve-instance like this:

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

Note that method dispatch is just about the only thing the result of class-prototype is good for; don't try to modify it or anything like that.

Outras dicas

Note that Common Lisp has the function SUBTYPEP:

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

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

See the documentation of SUBTYPEP for the meaning of the two return values (first says if it is a subtype). Classes are also types.

Which means that your functionality is just this:

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

Remember: inheritance in method works over class inheritance. That means to use the inheritance you have to pass an instance of a certain class:

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

Above takes an instance of class A.

Call it:

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 > 

There is a way to simplify^h^h^h^h^h^h^h^h make it easier to call: You can make sure that the class is finalized using something like CLOS:FINALIZE-INHERITANCE and the use a class prototype as input (calling CLASS-PROTOTYPE). That way you won't need to make instances of the class for dispatching. One would just use the prototype instance.

The alternative, ugly, version would be to hard-code the values:

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

(defmethod fun0 ((param (eql 'c)))
  T)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top