[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
New version of define-method-combination
- To: common-lisp-object-system@SAIL.STANFORD.EDU
- Subject: New version of define-method-combination
- From: Danny Bobrow <Bobrow.pa@Xerox.COM>
- Date: 20 Jan 87 12:11 PST
- Cc: Bobrow.pa@Xerox.COM
- Sender: Bobrow.pa@Xerox.COM
The following is an alternate version of define-method-combination.
It has the following properties:
1) There is only one define-method-combination form. It allows easy
support of around methods, and supports error checking. Defining simple
combinations is easy and concise.
2) There is no lambda-list. It also omits the keyword :order from the
method specifier. defgeneric-options is extended to store parameters
in the generic function. This can be used to support features like
reversed order for <and> combination if desired.
3) It is syntactically distinguishable from the current Flavors
version of define-method-combination, and hence allows backwards
compatiblity.
4) It uses the keyword :call-next-method instead of :around in
make-method-call. This eliminates confusion with respect to use of
this keyword with primary methods, and with respect to the new :around
keyword in method-combination options used to support around methods.
Thus
(make-method-call primary :around t)
becomes
(make-method-call primary :call-next-method t)
This renaming needs to be carried through to the functi
chapter.
Text that looks similar to that in the concep chapter was in fact taken
from there, and no changes were made in most paragraphs.
I am recommending that we include this instead of the
current version in concep.
*** Extension to defgeneric-options
defgeneric-options
{\bf :method-combination }
The list that is cdr of the option form is stored
in the generic-function object in the slot
{\it method-combination\/}.
Example:
(:method-combination and :most-specific-last)
stores {\it (and :most-specific-last)\/} in the slot
{\it method-combination\/}.
The body of compute-effective-method can get this information
from the generic-function using the slot-accessor.
***
*** Alternate version of define-method-combination***
\beginSection{Declarative Method Combination}
\beginsubSection{Defining Form for Method Combination}
The programmer can define new forms of method combination using the {\bf
define-method-combination} macro. This allows customization of step~3
of the method combination procedure. The body of {\bf
define-method-combination\/} used resembles {\bf defmacro\/} in that the
body is an expression that computes a Lisp form, usually using
backquote. Thus, an arbitrary combination of methods can be
constructed.
%The following syntax expressions need to be converted to TEX
(DEFINE-METHOD-COMBINATION name [macro]
({method-group-specifier}+ )
[({method-combination-option}*)]
{form}*)
{method-group-specifier}:= (variable { {qualifier-pattern}+ | predicate}
{keyword argument}*)
{method-combination-option} :=combination-keyword value
{\it name\/} is a symbol, usable as a name for this in
the {\bf :method-combination}
option to {\bf defgeneric-options} or {\bf defgeneric-options-setf}.
By convention, non-keyword, non-{\bf nil} symbols are usually used.
{\bf method-group specifiers\/}
A list of method-group specifiers follows the name.
Each specifier selects a subset
of the applicable methods to play a particular role, either by matching
their qualifiers against some patterns or by testing their qualifiers
with
a predicate. These method-group specifiers define all method qualifiers
that can be used with this type of method combination. If an applicable
method does not fall into any method-group, the system reports the error
that the method is not appropriate for the kind of method-combination
being
used.
Each method-group specifier names a variable. During the execution of
the
body forms, the variable is bound to a list of the methods in the
method-group, in most-specific-first order.
A qualifier-pattern is a list or the symbol {\bf *}. A method matches a
qualifier-pattern if the method's list of qualifiers is {\bf equal} to
the
qualifier-pattern, except that the symbol {\bf *} in a qualifier-pattern
matches anything. Thus a qualifier-pattern can be the empty list {\bf
()}
(which matches primary methods, which are always unqualified),
the symbol {\bf *} (which matches all
methods), a true list (which matches methods with the same number of
qualifiers as the length of the list, when each qualifier matches the
corresponding list element), or a dotted list that ends in the symbol
{\bf *} (the {\bf *} matches any number of additional qualifiers).
(True lists are non-dotted lists, as defined in {\it Common Lisp: The
Language\/}.)
Each applicable method is tested against the qualifier-patterns and
predicates in left-to-right order. As soon as a qualifier-pattern
matches
or a predicate returns true, the method becomes a member of the
corresponding method-group and no further tests are made. Thus if a
method
could be a member of more than one method-group, it joins only the first
such group. If a method-group has more than one qualifier-pattern, a
method need only satisfy one of the qualifier-patterns to be a member of
the group.
The name of a predicate function can appear in place of the
qualifier-patterns in a method-group specifier. The predicate is called
for each method that has not been assigned to an earlier method-group,
with
one argument, the method's qualifier list. The predicate should return
true if the method should be a member of the method-group. A predicate
is
distinguishable from a qualifier-pattern because it is a symbol other
than
{\bf nil} or {\bf *}.
Method-group specifiers can have keyword options after the
qualifier-patterns or predicate. Keyword options are distinguishable
from
additional qualifier patterns because they are not lists nor the symbol
{\bf *}. The keyword options are:
\def\numhangsize{60pt}
\numitem{{\bf :required} {\it boolean\/}} \break
If the argument is true (not {\bf nil}), and the method-group is empty
(that is, no applicable methods match the qualifier-patterns or satisfy
the
predicate), an error is signalled. This keyword option is a convenience
and does not add any expressive power. It is provided so that
programmers
are not tempted to omit error checking.
If {\bf :required} is not specified, it defaults to {\bf nil}.
The argument {\it boolean\/} is not evaluated.
\numitem{{\bf :description} {\it format-string\/}} \break
Programming environment tools use
% TEX won't let me use #' in the next line
{\bf (apply (function format) stream {\it format-string\/}
(method-qualifiers {\it method\/}))}
to print a concise description of a method's role (one or two
words). This keyword option allows the description of a method
qualifier to be defined in the same module that defines the
semantic meaning of the method qualifier. In most cases {\it
format-string\/} will not contain any format directives, but they
are available for generality. If {\bf :description} is not
specified, a default description is generated based on the
variable name, the qualifier patterns, and whether this
method-group includes the primary methods. The argument {\it
format-string\/} is not evaluated.
\def\numhangsize{25pt}
Individual implementations might support other keyword options.
Therefore it is required that implementations signal an error if
they observe a keyword option that is not implemented locally.
{\bf method-combination} options.
The list of method-combination options is optional, and is
distinguished from a form because it starts with a keyword.
The keyword options supported are:
{\bf :documentation} {\it string\/} documents the method-combination
type.
{\bf :around} {\it boolean\/}
If the argument is true (not {\bf nil}),
then the form returned by the body of define-method-combination
is augmented to support :around methods. See examples below.
This keyword option is a convenience and does not add any
expressive power. It is provided so that programmers are not
tempted to omit support for :around methods. Methods matching
(:around) are extracted first from the list of applicable
methods. If {\bf :around} is not specified, it defaults to
{\bf nil}. The argument {\it boolean\/} is not evaluated.
\def\numhangsize{25pt}
Individual implementations might support other method-combination
option; these must be keywords to ensure unambiguous interpretation.
Therefore it is required that all implementations signal an error if
they observe a keyword option that is not implemented locally.
The use of method-group specifiers provides a convenient syntax for the
cliched code that has to be written for every kind of method
combination,
to select methods, divide them among the possible roles, and perform the
necessary error checking. It is possible to perform further filtering
of
methods in the body forms, using normal list-processing operations and
the
functions {\bf method-qualifiers} and {\bf invalid-method-error}. It is
permissible to {\bf setq} the variables named in the method-group
specifiers and to bind additional variables. It is also possible to
bypass
the method-group specifier mechanism and do everything in the body
forms.
This is accomplished by writing a single method group with {\bf *} as
its
only qualifier-pattern; the variable is then bound to a list of all of
the
applicable methods, in most-specific-first order.
The body {\it forms\/} compute and return the Lisp form that specifies
how
the methods are combined, that is, the effective method.
The body of {\bf define-method-combination} resembles the body of
{\bf defmacro} and uses backquote in a similar way.
The function {\bf make-method-call} must be used in constructing the
Lisp form; it hides the implementation-dependent details of how
methods are called. Programmers always use {\bf make-method-call} to
translate from the lists of method objects produced by the method-group
specifiers to Lisp forms that invoke those methods.
Erroneous conditions detected by the body should be reported with
{\bf method-combination-error} or
{\bf invalid-method-error}; these functions
add any necessary contextual information to the error message and will
use
the condition signalling system when and if it is adopted into Common
Lisp.
The body {\it forms\/} are evaluated inside of the bindings of the
compute-effective-method lambda-list and method-group specifiers. The
variable {\it generic-function\/} is available in the body forms of the
combination definition. Since there are no declarations except inside a
user
form, declarations cannot effect bindings of variables in the
lambda-list
(e.g {\it generic-function\/} or any method-group variables.
The code may use any information obtainable from the particular
generic-function object.
The functions {\bf make-method-call}, {\bf method-combination-error},
and
{\bf invalid-method-error} can be called from the body {\it forms\/}, or
from functions called by the body {\it forms\/}. The action of these
three
functions can depend on dynamic variables bound by the object system
before
calling the method-combination function. These variables might contain
the
parameter list of the effective method or other implementation-dependent
information.
\endsubSection%{Defining Form for Method Combination}
\beginsubSection{Examples of define-method-combination}
%These examples could be put in the EXAMPLES field of the
DEFINE-METHOD-COMBINATION
%function description. I think it's better to put them into the
Concepts
%chapter and cross-reference them from the function description.
\screen!
;The default method-combination, the hard way
(define-method-combination standard
((around (:around))
(before (:before))
(primary ())
(after (:after)))
(unless primary
(method-combination-error "A primary method is required."))
(make-method-call `(,@around
(multiple-value-prog2
,(make-method-call before)
,(make-method-call primary :call-next-method t)
,(make-method-call (reverse after))))
:call-next-method t))
;The default method-combination using keyword options
(define-method-combination standard
((before (:before))
(primary () :required t)
(after (:after)))
(:around t)
(multiple-value-prog2
,(make-method-call before)
,(make-method-call primary :call-next-method t)
,(make-method-call (reverse after))))
;A simple way to try several methods until one returns non-nil
(define-method-combination and
((methods () (:and)))
(make-method-call methods :operator 'and))
;A more complete version of the preceding, adding around methods
and checking that there is a primary method.
(define-method-combination and
((primary () (:and) :required t))
(:around t :description "First method returning non-NIL"))
(make-method-call primary :operator 'and
:identity-with-one-argument t))
;Order methods by positive integer qualifiers
(define-method-combination example-method-combination
((methods positive-integer-qualifier-p))
(:around t)
(make-method-call (stable-sort methods #'<
:key #'(lambda (method)
(first (method-qualifiers method))))))
(defun positive-integer-qualifier-p (method-qualifiers)
(and (= (list-length method-qualifiers) 1)
(typep (first method-qualifiers) '(integer 0 *))))
\label Using Parameter Passing
;Using method-combination parameters,
;one can obtain parametric ordering for {\it and\/} combination.
(define-method-combination and
((primary () (:and) :required t))
(:around t))
(when (apply order-parameter
(cdr (method-combination generic-function)))
(setq primary (reverse primary)))
(make-method-call primary :operator 'and
:identity-with-one-argument t))
(defun order-parameter (&optional (order :most-specific-first))
(case order
(:most-specific-first ())
(:most-specific-last t)
(otherwise (method-combination-error ''~S is an invalid order.~@
:most-specific-first and :most-specific-last are the possible
values.''
order)))
\endscreen!
\endsubSection%{Examples of define-method-combination}
\endSection%{Declarative Method Combination}
*** End Declarative Method combination***