[Openmcl-devel] Applying sequence functions to a new type

Gary Byers gb at clozure.com
Tue Mar 1 21:18:59 PST 2005

On Tue, 1 Mar 2005, Andrew P. Lentvorski, Jr. wrote:

> On Mar 1, 2005, at 4:52 AM, Gary Byers wrote:
> >
> > On Mon, 28 Feb 2005, Andrew P. Lentvorski, Jr. wrote:
> >
> >> This is probably a really stupid question, but how do I define the
> >> sequence functions so that they apply to a new class?
> >
> > You basically can't reliably subclass BUILT-IN-CLASSes like SEQUENCE
> > (or FLOAT or FIXNUM) in CLOS.
> >
> > The standard sequence functions are not necessarily GENERIC-FUNCTIONs
> > (they aren't in OpenMCL or in any other implementation I'm aware of),
> > so you can't extend their behavior by adding methods on them.
> Hrm.  Well, this seems like a really nasty limitation.  Is there a
> reason that they are not GENERIC-FUNCTIONS other than accident of
> history?  Especially since when I dig through the source
> implementations of some of these functions, they spend an awful lot of
> code working out exactly what type they just got passed.  It would seem
> like letting the GENERIC-FUNCTION machinery do those checks would save
> a lot of code.  I'm sure I'm missing something obvious though
> (something stupid like CLOS isn't available when those functions are
> needed or something).

One factor is that types like SEQUENCE (and LIST, and NUMBER, and FIXNUM)
existed for a long time before CLOS did; large bodies of code had and
continue to have very concrete ideas about what it means for something
to be a NUMBER or a SEQUENCE or a FIXNUM or a BIT or a PACKAGE or ...

People might agree that the "Gray streams proposal" - which says, basically,
that practically anything can behave as a stream as long as certain
methods are defined on it - is a reasonable way of gaining extensibility
and generality without sacrificing too much (the built-in class STREAM
is pretty "abstract" to begin with.)  So, we -could- do something like:

(defmethod streamp ((l list))
  "With Gray streams, STREAMP is a generic function."

(defmethod stream-write-char ((stream list) char)
  "Maybe this was a bad example ..."
  (nconc l (list char)))

This may be a good example of "having enough rope to hang yourself", and
I'd agree that that's generally a good thing.  (I mean the "having enough
rope" part, of course.)

At the other extreme, some BUILT-IN-CLASSes are pretty concrete; it'd
arguably be less of a good idea to make INTEGER extensible:

(defgeneric integerp (x)
   "It's an integer if I say it is! (and if I define a few methods.)")

I think that many people would agree that that (or the ability to
subclass INTEGER, or the ability to subclass BIT, or the ability to
subclass CONS ...) is probably a little too much rope.  Arguments in
favor of that position tend to be of the form "whether or not something's
a BIT isn't really a question of whether or not the generic function
BIT-P says it is, or whether or not it implements certain BIT methods;
programs and programmers have a much more concrete notion of what a
BIT is, and it would be a big mistake to violate those assumptions."

In some programming environments, LIST is an abstract data type: any
class that implements a CONStructor method and operations like CAR/CDR
(or FIRST/REST or HEAD/TAIL ...) pretty much satisfies the LIST
protocol.  In Common Lisp, LIST denotes a very concrete data type
(precisely (OR CONS NULL)), where CONS and NULL are also very concrete
data types.  There's nothing that prevents you from defining an
abstract "LIST-like" class or protocol, but CLOS does prevent you from
making that class a subclass of CL:LIST and from using #'CL:CONS,
#'CL:CAR, and #'CL:CDR to implement that protocol.

In some programming environments, SEQUENCE is an abstract data type.
In CL, SEQUENCE denotes precisely the type (OR VECTOR LIST); it does
not denote "anything that people might someday think of as being
sequence-like in some sense" any more than LIST (or BIT ...) denote
something that ... generic.

When CLOS was introduced into CL, someone could have said "You know,
CL's notion of what a SEQUENCE is is too restrictive; someday, users
may think of a SEQUENCE as being an abstract data type, and we should
change the language to allow that."  (David Gray -did- say almost
exactly that about STREAM in that time frame.)  As far as I know and
remember, no one said that.  I think that it would have been a hard
sell, since there was already a lot of code which had a pretty clear
(rigid) idea of what it meant for something to be of type CL:SEQUENCE,
and of what it meant for something -not- to be of that type.  I'm not
sure if it'd be an easier sell today.

Note that very little of this has anything to do with concerns over
the efficiency of generic-function dispatch (though it'd probably be
equally unfair to suggest that efficiency concerns are completely
irrelevant.)  This message is already a little too long; if people
read my message earlier today and were left with the impression
that CLOS contains a "nasty limitation" through lack of foresight
or sophistication on the part of its designers or adopters (or that
no one thought about these issues deeply 15 years ago, or that there
wasn't any debate or consideration given to other approaches ...) well,
I may have been a little too terse.  I personally believe that the
decision to make large parts of the type/class hierarchy closed (non-
extensible) was more right than wrong, and I wish that I could be a
little more succinct in explaining why I believe that.

People may be left with the impression that Common lisp really should
have an extensible abstract protocol for defining SEQUENCE-like methods
on SEQUENCE-like objects.  There is not (and never has been) anything
stopping anyone from developing such a library, aside from the fact that
designing something general and usable is hard.

More information about the Openmcl-devel mailing list