Search code examples
oopcommon-lisplanguage-designclosmethod-combination

CLOS: Method combination with arbitrary function


While reading about CLOS (in ANSI Common Lisp by Paul Graham), I noticed that there are nine functions that can be given to defmethod as its second argument: +, and, append, list, max, min, nconc, or and progn. According to this answer, they are called simple method combinations.

Question

Why only these nine? What is the reason I cannot pass an arbitrary function as the second argument?

Example of what I would like

Suppose I defined xor as

(defun xor (&rest args)
   (loop for a in args counting (not (null a)) into truths
      finally (return (= truths 1))))

(this could certainly be improved). I would like to define several classes describing clothes and their combinations using xor:

(defgeneric looks-cool (x)
   (:method-combination xor))

(defclass black-trousers () ())
(defclass quilt () ())
(defclass white-shirt () ())
(defclass hawaii-shirt () ())

(defmethod looks-cool xor ((tr black-trousers)) nil)
(defmethod looks-cool xor ((qu quilt)) t)
(defmethod looks-cool xor ((ws white-shirt)) nil)
(defmethod looks-cool xor ((hs hawaii-shirt)) t)

(defclass too-stiff (black-trousers white-shirt) ())
(defclass scottish  (quilt white-shirt) ())
(defclass also-good (black-trousers hawaii-shirt) ())
(defclass too-crazy (quilt hawaii-shirt) ())

Now if this compiled (which it doesn't), I would be able to use Lisp to guide me as to what to wear:

> (looks-cool (make-instance 'too-stiff))
  NIL
> (looks-cool (make-instance 'scottish))
  T
> (looks-cool (make-instance 'also-good))
  T
> (looks-cool (make-instance 'too-crazy))
  NIL

I am well aware that this is a rather artificial example of no practical importance. Still, I would like to know whether there is some deeper reason behind or whether the restriction to the nine functions is just to make implementation easier.


Solution

  • Use the standard Common Lisp macro DEFINE-METHOD-COMBINATION to define your own simple method combinations:

    Example:

    (define-method-combination xor :identity-with-one-argument t)
    

    Then:

    CL-USER 5 > (mapcar #'looks-cool
                        (list (make-instance 'too-stiff)
                              (make-instance 'scottish)
                              (make-instance 'also-good)
                              (make-instance 'too-crazy)))
    (NIL T T NIL)
    

    If we look at (define-method-combination xor :identity-with-one-argument t), it has several meanings for the name xor:

    • it uses an operator xor - a function, macro or special form - not only functions are allowed. If the operator name should be different from the method combination name -> use the :operator keyword to specify that.

    • it defines a method combination named xor. This name can be used in defgeneric.

    • it defines a method qualifier xor. This can be used in defmethod.

    Note that one can also define more complex method combinations with that DEFINE-METHOD-COMBINATION.