David Bakhash <·····@bu.edu> writes:
> why do all methods of a generic function have to have congruent
> arglists?
This question divides into two questions:
1. Why does the congruency requirement forbid certain configurations
that seem conceptually to satisfy the congruency intent though
not the letter of the rules.
Answer: I don't know. I complained about this when it happened
and no one seemed to care.
2. Why must a function take uniform calling conventions?
Answer: I *suspect* this is a hint that you are trying to do what I call
"overloading" rather than what I call "genericity". I use the former term
to refer to accidental kludges that someone threw in because they had the
bogus belief of generality. e.g., the use of "foo"+"bar"=>"foobar" in
Java is and overloading issue in my personal terminology, not a genericity
issue. In this case, the args are congruent and so congruency can be seen
not to catch all such cases, but if you had a function named
PITCH that took no args to mean "throw a baseball" and one arg to mean
"toss into the trash", then you'd have a congruency mismatch that was
catching you doing the overloading thing that CL does not want you to do.
Genericity it defined by the DEFGENERIC, for which there should be only
one (or if multiple, they should be equivalent), and all the DEFMETHODs
don't define the generic (except by the uninteresting favor they do you
of doing an implicit ensure-generic-function (i.e., DEFGENERIC)).
So what would it mean to have multiple ways to conform to a single way
of doing things? The defgeneric defines the protocol and that's the end
of the story. You can only redefine it or leave it the same without
introducing overloading.
The design comes from (for example) Lisp Machine Flavors,
where everything was
(SEND X0 :FOO X1 X2 ...)
but this meant the compiler could never flag type errors or syntax errors
because it didn't know what args X might take. The change was made to
do
(FOO X0 X1 X2 ...)
so that you could detect errors of argument type, number, etc.
It is a good property of a language that its legal sentences are sparsely
arranged because a compiler can tell good code from bad. Lisp pushes
the boundaries of this by declaring a great many things ok that are
questionable in other languages. But this is an area where a large body
of programming showed that many bugs resulted from the availability of
this flexibility you ask for and very little was lost by removing it.
(This is why people like static typing--I just think they've carried it
to extremes. The ability to add static typing is good as an optional
thing; the requirement to statically type is bad, IMO. But it's this
sparseness thing they're motivated by in having the design.)
Anyway, if FOO still just took &REST WHATEVER (which is what you are saying
when you ask for no congruency checking, the compiler couldn't do that.)
Every defmethod would be changed from
(defmethod foo (...args...) ...blah...)
to
(defmethod foo (&rest args)
(destructuring-bind (...args...) ...blah...))
But then you also wouldn't know what to dispatch off of. So the
system especially wants you to agree about the required args.
If you allow
(defmethod foo ((x a) (y b) &optional z) ...blah...)
(defmethod foo ((x a) (y b) (z c)) ...quux...)
which of these will take precedence? it's hard to know if this means
the second replaces the first and the arg isn't optional, or if
the first merely replaces the "if supplied" part of the first, leaving
the first equivalent to
(defmethod foo ((x a) (y b)) ...blah...)
and it's hard to know what to do with another method
(defmethod foo ((x a) (y b)) ...antiblah...)
Allowing different methods would also leave an ordering problem no
different than the question of whether "foo1" is ordered before or
after "foo" in an alphabetical ordering. This is a definitional issue,
not a conceptual one, so it's scary to make the core engine of your
dispatch engine for function calls rely on something arbitrary like
this. By making all methods congruent, you implicitly make all methods
have an intuitive ordering when competing for availability in an effective
method computation.
I kinda typed that out quickly but I hope it's coherent enough for you
to slug through.
In article <···············@engc.bu.edu>, David Bakhash <·····@bu.edu> wrote:
>why do all methods of a generic function have to have congruent
>arglists?
See the thread that was posted the last time this was asked:
<http://www.dejanews.com/[ST_rn=ps]/dnquery.xp?search=thread&······························@milo.jpl.nasa.gov%3e%231/1&svcclass=dnserver>
--
Barry Margolin, ······@bbnplanet.com
GTE Internetworking, Powered by BBN, Burlington, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
David Bakhash <·····@bu.edu> writes:
>
> why do all methods of a generic function have to have congruent
> arglists?
How about 4 answers:
LEGALISTIC ANSWER:
Because the ANSI standard says so.
IMPLEMENTATION ANSWER:
Because methods of generic functions can dispatch on the types of more
than one argument. In the interests of avoiding ambiguous cases, all of
the arguments that could potentially be used for method dispatch must be
the same across all methods of a generic function.
Motivating example:
If this were allowed:
(defmethod f ((x INTEGER) &rest otherArgs) ...) ;; F1
(defmethod f ((x INTEGER) (y INTEGER) &rest otherArgs) ...) ;; F2
then which is the correct method to use for a call to:
(f 1 2 3 4 5) ??
both F1 and F2 match.
Since you can provide keyword arguments, you can in fact have methods
that take different arguments. The differences just need to be in the
keywords, not in the required arguments.
PHILOSOPHIC ANSWER:
Because all methods of a generic function are supposed to compute "the
same thing" in some general over-arching sense. Computing the same
thing is a somewhat tenuous concept, but it motivates the desire to have
a common interface to the function.
One principle of object-oriented design is that if a program operates on
objects of type T, you should be able to use any subtype ST of T in the
same code and have the code function properly. Now if the arguments of
a generic function F are not congruent, it would limit your ability to
substitute ST for T in the code -- since the method on F for ST could
take different REQUIRED arguments than the method on F for T.
If you have methods with this behavior, it suggests some potential
design problems in your program:
(1) A flaw in the object type-subtype hierarchy, since the sub type is
not in fact a good substitute for its parent.
(2) A violation of the abstraction notion. This is related to point
(1). What it means is that the code in question relies on some
particular property of the specific type of object. This would
indicate that the program isn't really concerned with doing a
generic operation on a whole class of objects, but rather with
doing a particular operation to some subset of this class. In
other words, the programmer already has to know which subtype
of object he is working with, because otherwise he wouldn't know
which set of arguments to give to the generic function. This in
essence forfeits the benefit of using the object abstraction and
method dispatch in the first place. Why not just use a
non-generic function instead? What benefit is gained from the
generic function?
(3) Conflation of two separate, but closely (?) related elements of
functionality in the same generic function. If there really is
a difference in what is being processed, then perhaps there should
be separate names for the functions.
SOCRATIC ANSWER:
How is this hampering you? Do you have an example?
--
Thomas A. Russ, USC/Information Sciences Institute ···@isi.edu
···@sevak.isi.edu (Thomas A. Russ) writes:
> David Bakhash <·····@bu.edu> writes:
>
> >
> > why do all methods of a generic function have to have congruent
> > arglists?
<snip>
> If this were allowed:
>
> (defmethod f ((x INTEGER) &rest otherArgs) ...) ;; F1
> (defmethod f ((x INTEGER) (y INTEGER) &rest otherArgs) ...) ;; F2
>
> then which is the correct method to use for a call to:
>
> (f 1 2 3 4 5) ??
>
> both F1 and F2 match.
CLOS already says the most specific match wins, so it'd have to be the
latter.
--
Harvey J. Stein
BFM Financial Research
·······@bfr.co.il
Harvey J. Stein wrote:
>
> ···@sevak.isi.edu (Thomas A. Russ) writes:
>
> > David Bakhash <·····@bu.edu> writes:
> >
> > >
> > > why do all methods of a generic function have to have congruent
> > > arglists?
> <snip>
> > If this were allowed:
> >
> > (defmethod f ((x INTEGER) &rest otherArgs) ...) ;; F1
> > (defmethod f ((x INTEGER) (y INTEGER) &rest otherArgs) ...) ;; F2
> >
> > then which is the correct method to use for a call to:
> >
> > (f 1 2 3 4 5) ??
> >
> > both F1 and F2 match.
>
> CLOS already says the most specific match wins, so it'd have to be the
> latter.
Well, there's having the CLOS spec produce decidable algorithms, and
then there's having it produce efficiently computable ones.
It may or not be that one COULD compute an algorithmic ordering of
methods for all possible cases without needing concgruent arglists.
However, it is not obvious to me how to provide even the most basic
caching optimizations without it.