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

Method Combination Objects



There haven't been any dissenting comments, as I assume the design for
method combination meta objects agreed on by Danny and me is accepted
by everyone else.

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 sorted list of applicable
  methods, and the method combination instance."
Append to the paragraph:
  "A method combination instance 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
instances to the list of meta-objects.  Each method combination instance
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
  instance.  A method combination instance 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 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-instance generic-function
                             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-instance.  remove-method can be used to
    undefine a method combination type.

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

    method-combination-instance 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-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-instance 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
                                     methods
                                     (mc short-form-method-combination))
  (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)))
  ;;--- Note: this example has not been updated to reflect the removal
  ;;--- of make-method-call.  If we do that, the update should be
  ;;--- straightforward.
  (make-method-call `(,@around-methods
                      ,(make-method-call primary-methods
                                         :operator (slot-value mc 'operator)
                                         :identity-with-one-argument
                                           (slot-value mc 'identity-with-one-argument)))
                    :operator :call-next-method)))

(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-instance
              (generic-function
               (name (eql ',name))
               options)
     (declare (ignore generic-function))
     (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 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
          (standard-method-combination)
          ())

(defmethod method-combination-instance
           (generic-function
            (name (eql 'standard-with-or))
            options)
  (declare (ignore generic-function))
  (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
                                     methods
                                     (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))
                                                 or-methods)
                                       ,subform)
                                  t)
                          ;; Leave all other subforms of effective method alone
                          subform))
                  (call-next-method generic-function other-methods mc))))