[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Method Combination Objects

At the Cambridge meeting I promised to mail this out.

Currently method combination types are represented in a kludgey fashion,
as a pair of objects: a symbol that names the type, and a list of options.
It would be better to do this in an object-oriented way, the same as
everything else.  Here is a proposal for how it would look.  Method
combination objects are meta objects, so this is all chapter 3 material.
There are a couple of minor modifications to chapters 1 and 2, which I
will note at the end.

Method Combination Naming Layer

This layer is concerned with mapping method combination type names, as
seen in the :method-combination option to defgeneric, to method
combination objects.

(method-combination-maker name) => function

This function maps the name of a method combination type into a function
that accepts the options seen in the :method-combination option to
defgeneric and returns the method combination object.
method-combination-maker signals an error if -name- is unrecognized.
-function- should signal an error if the options are unrecognized.

(apply (method-combination-maker name) options) is how a method combination
name and options are converted into an object.

(setf (method-combination-maker name) function) is how a method combination
type is defined.

(setf (method-combination-maker name) nil) is how a method combination
type is undefined.  (Or we could have a separate function for this.)

(method-combination-name method-combination) => symbol
(method-combination-options method-combination) => list

These two generic functions perform the inverse mapping.

Why we have method-combination-maker instead of just using make-instance:
 - The names of method combination types that we have chosen are not
   appropriate as names of classes.  These names were chosen on the
   assumption that they would be in their own namespace.  Thus either we
   have to have a new function to map from the method combination name
   to the class object, or we have to change all the names.  Changing
   all the names would make the programmer interface awkward.
 - The options seen in the :method-combination option to defgeneric
   are defined by a lambda-list in define-method-combination, rather
   than being just keyword arguments.  Hence it makes sense to receive
   these options with a function.  We could change these options into
   keywords suitable for use as initialization arguments, but this might
   make the programmer interface awkward.  If we just passed the whole
   list of options as one :options initialization argument, we wouldn't
   be exploiting any Lisp mechanism to parse the options.
 - Several method combination types might share a single class.
 - A single method combination type name might map into several different
   classes, depending on the options.
 - A function returned by method-combination-maker might return the same
   object each time it is called, instead of making a new object.
For these reasons it seems better to interpose an extra layer between
make-instance and the name and options of a method combination type.

Method Combination Object Layer


This class is a superclass of all classes of method combination.


This class is the class of the method combination object used by default
when :method-combination is not specified.

Other implementation-dependent subclasses of method-combination exist.
For example, all invocations of the short form of
define-method-combination probably use one class, and each invocation of
the long form of define-method-combination probably defines a new class
which has a superclass in common with standard-method-combination.
CLOS does not specify how many of these classes there are nor what
their names are.

(compute-effective-method generic-function method-list method-combination)
  => effective-method-form

This generic function performs part 3 of the determination of the
effective method.  define-method-combination works through methods that
specialize the third parameter.

(describe-method-concisely generic-function method method-combination)

This generic function prints a description of the method onto
*standard-output*.  The value returned is ignored.
define-method-combination defines a method for describe-method-concisely
that uses the :description option of the long form to control
what it prints.

Other generic functions specialized by method combinations are not
currently defined by CLOS, but program development environments are
likely to have some.

A generic function object records a method combination object, rather
than the name and options of a method combination type.  This changes
the initialization arguments and structural access functions for
generic functions from what is in chapter 3 now.  defgeneric calls
method-combination-maker before it calls ensure-generic-function.


The short form of define-method-combination could have been defined
as follows:

(defclass short-form-method-combination
	  ((name :initarg name :reader method-combination-name)
	   (order :initarg order)
	   (documentation :initarg documentation :reader documentation)
	   (operator :initarg operator)
	   (identity-with-one-argument :initarg identity-with-one-argument)))

(defmethod method-combination-options ((mc short-form-method-combination))
  (list (slot-value mc 'order)))

(defmethod compute-effective-method (generic-function
				     (mc short-form-method-combination))
  (let ((primary-methods (remove (list (slot-value mc 'name))
				 methods :key #'method-qualifier
				 :test-not #'equal))
	(around-methods (remove '(:around)
				methods :key #'method-qualifier
				:test-not #'equal)))
  (when (eq (slot-value mc 'order) ':most-specific-last)
    (setq primary-methods (reverse primary-methods)))
  (dolist (method (set-difference methods
				  (union primary-methods around-methods)))
    (invalid-method-error method "The qualifiers of ~S, ~:S, are not ~S or ~S"
			  method (method-qualifiers method)
			  (list (slot-value mc 'name)) '(:around)))
  (make-method-call `(,@around-methods
		      ,(make-method-call primary-methods
					 :operator (slot-value mc 'operator)
					   (slot-value mc 'identity-with-one-argument)))
		    :operator :call-next-method)))

(defmethod describe-method-concisely
	    (method-combination short-form-method-combination))
  (declare (ignore generic-function))
  (write-string (string-downcase (string (first (method-qualifiers method))))))

(defmacro define-method-combination
	  (name &key (documentation nil)
		     (operator name)
		     (identity-with-one-argument nil))
  `(setf (method-combination-maker ',name)
	 #'(lambda (&optional (order ':most-specific-first))
	     (make-instance 'short-form-method-combination
			    'name ',name
			    'order order
			    'documentation ',documentation
			    'operator ',operator
			    'identity-with-one-argument ',identity-with-one-argument))))

Example of Defining a Method Combination Type via Inheritance

;This example defines a method combination type that is similar
;to standard method combination, except that it also allows :or
;methods.  The :or methods are executed after the :before methods,
;before the :after methods, inside the :around methods, and before
;the primary method.  The primary method is only called if all the
;:or methods return nil; if any :or method returns non-nil, its
;value becomes the value of the generic function (or the value
;returned by call-next-method in the least specific :around method)
;in place of the values of the most specific primary method.

;This assumes approach 2 or 3 to making effective method code
;analyzable, and assumes one particular code analysis tool, whose
;details I will not try to explain here.
;Those assumptions are not critical.

;I'm assuming we don't want to try to extend the define-method-combination
;macro so that it could exploit inheritance.  Instead I will
;define the example directly in terms of the next layer down.

(defclass standard-method-combination-with-or

(setf (method-combination-maker 'standard-with-or)
      #'(lambda () (make-instance 'standard-method-combination-with-or)))

;This uses call-next-method to get the effective method in the absence
;of any :or methods, then it modifies the effective method form to
;incorporate the :or methods in an OR special form wrapped around the
;call to the most specific primary method.
(defmethod compute-effective-method (generic-function
				     (mc standard-method-combination-with-or))
  (let ((or-methods (remove '(:or) methods :key #'method-qualifiers
			    :test-not #'equal))
	(other-methods (remove '(:or) methods :key #'method-qualifiers
			       :test #'equal)))
    (lt:copyforms #'(lambda (subform kind usage)
		      (declare (ignore usage))
		      (if (and (listp kind) (listp subform)
			       (eq (first subform) 'method-call)
			       (null (method-qualifiers (second subform))))
			  ;; Put or methods before primary method 
			  (values `(or ,@(mapcar #'(lambda (method)
						     `(method-call ,method))
			  ;; Leave all other subforms of effective method alone
		  (call-next-method generic-function other-methods mc))))

Modifications to Chapters 1 and 2

1-31: The arguments to compute-effective-method have been changed.

2-43: The value of the :method-combination argument to
ensure-generic-function becomes a method combination object.  Currently,
the value of this argument isn't really documented at all.