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

Method Combination Objects



Revised, based on Gregor's and Jim's comments.  Nothing new here, just
amalgamation of previous mail.

First, the impact on chapters 1 and 2.  Page numbers refer to the 20 Jan
1988 drafts.

1-33 last paragraph (excluding the implementation note), second sentence.
Replace this sentence with:
 "The generic function {\bf compute-effective-method} receives as
  arguments the generic function, the method combination object,
  and the sorted list of applicable methods."
Append to the paragraph:
  "A method combination object is a meta-object that encapsulates
  the method combination type and options specified by the
  {\bf :method-combination} option to forms that specify generic
  function options."

1-40: This is optional, but I think we should add method combination
objects to the list of meta-objects.  Each method combination object
is an instance of a subclass of the class {\bf method-combination}.
I don't think we need to name any of the subclasses here, not even
{\bf standard-method-combination}; that's chapter 3 material for now.

2-42 documentation method signatures: Add two for method-combination
(italic) method-combination (bold), for documentation and for
setf of documentation.  On the next page, under arguments, the first
bullet should mention method combination objects too.

2-43 ensure-generic-function arguments: Nothing really needs to be
changed, since the value of the :method-combination argument is not
described at all in the latest draft.  However, I suggest two sentences
should be added:
  "The {\bf :method-combination} argument is a method combination
  object.  A method combination object is a meta-object that
  encapsulates the method combination type and options specified by the
  {\bf :method-combination} option to forms that specify generic
  function options."

That's all that has to be changed in chapters 1 and 2.  Here's an
approximation of what goes into chapter 3.  I have done no editing for
style here, only content.  I've left out the design rationale this
time, to keep this small.  Consult the referenced messages of the
referenced messages if you want to see it again.

Method Combination Naming Layer

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

(method-combination-object method-combination-name
			   method-combination-options)
	   => method-combination

    is how a method combination name and a list of options are converted
    into an object.  define-method-combination expands into a defmethod
    for method-combination-object.  remove-method can be used to
    undefine a method combination type.

    method-combination-object signals an error if
    method-combination-name is unrecognized.  Each method for
    method-combination-object signals an error if the
    method-combination-options are unrecognized or there are
    too many or too few of them.

    method-combination-object might return the same object each time
    it is called with given arguments, or it might make a new object.

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

    These two generic functions perform the inverse mapping.

Method Combination Object Layer

method-combination

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

standard-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-combination method-list)
  => 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 second parameter.

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

    This generic function prints a description of the method's role onto
    *standard-output*.  The value returned is ignored.  The description
    is generally determined jointly by the method-combination and the
    method's qualifiers.  The description describes only the method's
    role, not its generic function and not its parameter specializers;
    those would be described by the caller or implied by context (e.g.
    if the caller is printing a table of methods for a particular
    generic function).  define-method-combination defines a method for
    describe-method-role-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.  There will be a structural
access function that, given a generic function object, returns a method
combination object.  defgeneric calls method-combination-object before
it calls ensure-generic-function.

Example

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

(defclass short-form-method-combination
	  (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)
				     methods)
  (let ((primary-methods (remove (list (slot-value mc 'name))
				 methods :key #'method-qualifiers
				 :test-not #'equal))
	(around-methods (remove '(:around)
				methods :key #'method-qualifiers
				: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)))
    (let ((form (if (or (rest primary-methods)
			(not (slot-value mc 'identity-with-one-argument)))
		    `(,(slot-value mc 'operator)
		      ,@(mapcar #'(lambda (method)
				    `(call-method ,method ()))
				primary-methods))
		    `(call-method ,(first primary-methods) ()))))
      (if around-methods
	  `(call-method ,(first around-methods)
			(,@(rest around-methods)
			 (make-method ,form)))
	  form))))

(defmethod describe-method-concisely
	   (generic-function
	    method
	    (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))
  `(defmethod method-combination-object
	      ((name (eql ',name))
	       options)
     (apply #'(lambda (&optional (order ':most-specific-first))
		(check-type order (member :most-specific-first
					  :most-specific-last))
		(make-instance 'short-form-method-combination
			       'name ',name
			       'order order
			       'documentation ',documentation
			       'operator ',operator
			       'identity-with-one-argument
				 ',identity-with-one-argument))
	    options)))

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 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
	  (standard-method-combination)
	  ())

(defmethod method-combination-object
	   ((name (eql 'standard-with-or))
	    options)
  (unless (null options)
    (error "standard-with-or method combination does not accept options"))
  (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)
				     methods)
  (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) 'call-method)
			       (null (method-qualifiers (second subform))))
			  ;; Put or methods before primary method 
			  (values `(or ,@(mapcar #'(lambda (method)
						     `(call-method ,method ()))
						 or-methods)
				       ,subform)
				  t)
			  ;; Leave all other subforms of effective method alone
			  subform))
		  (call-next-method generic-function mc other-methods))))