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

proposed syntactic cleanups in defmethod



We've noticed some problems with the CLOS syntax for defmethod,
especially when using methods on individuals, and in response I'd like
to propose three cleanups.  Note that all of these changes are purely
syntactic; that is, they only affect the readability of programs, they
don't add or remove capabilities of the language.  I hope this is a
minor issue that can be dealt with quickly.

The first problem has to do with the suggested compiler warnings
mentioned on CLtL p. 160 under the IGNORE declaration.  Methods often
don't use some of their parameters for anything other than making the
method applicable for certain arguments to the generic function.
Consider this example

  (defmethod additional-mail-headers ((host 'xerox.com))
    (declare (ignore host))
    '(:line-fold "No"))

One would prefer not to have to write the (declare (ignore host)).
This almost always happens with methods on individuals, but it can
also happen with methods on classes.  Consider

  (defmethod color ((e elephant))
    (declare (ignore e))
    'grey)

I propose that the DEFMETHOD macro be specified to "refer to" (in the
sense of CLtL p.160) each specialized parameter.  This means that a
compiler warning will not occur regardless of whether the body of the
method does or does not refer to the parameter, and the declare ignore
in the above examples must be removed.  This makes sense intuitively
if one regards the type check of the argument against the parameter
specializer as being part of the method; thus any specialized parameter
is referred to by the type check.

An interesting effect of this is that

  (defmethod part-color ((e elephant) part)
    'grey)

and 

  (defmethod part-color ((e elephant) (part t))
    'grey)

are no longer synonymous, since the first gives a compiler warning
and the second does not.  I think this is a feature, not a bug.

================

The second problem relates to the use of the single quote syntax (') for
methods on individuals.  There are actually two problems here.  One is
that that single character is easy to overlook.  One of the most common
mistakes of beginning Lisp programmers is omitting quote marks or putting
them in the wrong place.  Actually, it's not only beginning programmers
who do this.  I still do it myself sometimes, and I believe I have seen
Danny Bobrow do it.

I believe the programmer who intended to write

  (defmethod color ((e elephant))
    'grey)

but accidentally wrote

  (defmethod color ((e 'elephant))
    'grey)

instead is going to have a lot of trouble figuring out what happened.
It would be better to make the latter form a syntactic error, so that
we can give a clear error message.  We should use a different syntax for
methods on individuals.  The quote mark is a cute hack, but I think we
can afford to use something slightly more verbose.  Suggestion below.

The other problem with methods on individuals is that the current syntax
is awkward when the individual is anything other than a symbol or a
number, just because defmethod is using a special magic syntax instead
of a normal Lisp form.  Returning to my first example, suppose hosts are
to be represented by CLOS objects rather than symbols.  Instead of
writing

  (defmethod additional-mail-headers ((host 'xerox.com))
    '(:line-fold "No"))

I now have to write

  (defmethod additional-mail-headers ((host '(parse-host "xerox.com")))
    '(:line-fold "No"))

but this doesn't work, because the form (parse-host "xerox.com")
is not in a position to be evaluated.  I can write

  (eval `(defmethod additional-mail-headers ((host ',(parse-host "xerox.com")))
	   '(:line-fold "No")))

but that's grotesque.  I can also write

  (defmethod additional-mail-headers ((host '#.(parse-host "xerox.com")))
    '(:line-fold "No"))

which works for this case, but #. has scoping problems exposed by the
next example.  Suppose we're writing methods on individual numbers, but
rather than sprinkle numbers all over the code, we use defconstant, as
good style dictates.  This is boiled down from a remote-procedure-call
example.

  (defconstant begin-code 1)
  (defconstant end-code 2)
  (defconstant integer-code 3)
  (defconstant float-code 4)

  (defmethod decode (stream (opcode '#.begin-code))
    ... begin processing a message ...)

One problem with this occurs when using compile-file.  CLtL isn't very
clear on whether #. evaluates in an environment in which those
defconstants have been evaluated.  It's likely that the programmer has
to play some games with eval-when to make this work.  This could be
fixed by tightening up the semantics of compilation in Common Lisp, but
a worse problem is that #. does not and can not respect lexical scoping.
Suppose we had

  (let ((begin-code 1))
    (defmethod decode (stream (opcode '#.begin-code))
      ... begin processing a message ...))

There is no way the #. can see the environment created by the LET
which is still being read in at the time the #. is processed.

It seems that it would make a lot more sense to put a regular Lisp form
in the parameter-specializer-name, scoped completely normally, and let
the defmethod macro (rather than the programmer) take care of any
special mechanism needed to evaluate with respect to the proper
environment, including compile-time defconstants.  Common Lisp doesn't
currently standardize all the primitives needed to write that defmethod
portably, but I think that only strengthens the argument for making
defmethod worry about those issues, instead of making every programmer
worry about them.

So I'd like to see the syntax of an individual
parameter-specializer-name changed to use a different word instead of
QUOTE, and to use a form that evaluates to the object, instead of the
object itself.  The form is evaluated at the time the method is defined;
it is not evaluated each time the generic function is called.  I have
two suggestions for what word to use, one based on what test is being
performed and the second based on the Common Lisp type system:

  (defmethod additional-mail-headers
	     ((host (eql (parse-host "xerox.com"))))
    '(:line-fold "No"))

  (defmethod additional-mail-headers
	     ((host (member (parse-host "xerox.com"))))
    '(:line-fold "No"))

I mildly prefer EQL over MEMBER, but I thought I'd open up both
suggestions for discussion.  Each of these suggests an obvious
generalization, EQL to other predicates and MEMBER to multiple
individuals, and the choice might be based on which generalization we
want people to think about, even if we don't propose to implement the
generalization.

================

The final issue has to do with parameter-specializers rather than
parameter-specializer-names, using the terminology of 87-002 page 1-18.
I think that adding QUOTE as a type-specifier to Common Lisp is both
unnecessary and confusing.  (Yes, I know I suggested it.  I was wrong.)
Instead, the parameter-specializer for a method on an individual should
be (MEMBER object), the type-specifier that Common Lisp already defines
for this purpose.  Note that there is no particular reason why the
parameter-specializer should be the same as the parameter-specializer-name;
they're already not the same for methods on classes.