[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Comments on 87-002, Chapter 1
- To: common-lisp-object-system@sail.stanford.edu
- Subject: Comments on 87-002, Chapter 1
- From: Jim Kempf <kempf%hplabsc@hplabs.HP.COM>
- Date: Mon, 23 Feb 87 11:35:56 pst
Comments are listed in order of how important I think the problems with the document
are.
A) REDEFINING CLASSES
I have serious problems with this section. Coming at it from an implementor's
viewpoint, there are some potential portability holes (port holes? :-) ) in
the document that would be well to catch before they become cast in concrete.
Additionally, this section, in my opinion, slants the entire CLOS in the
direction of a prototyping vehicle rather than a vehicle for implementing
production code. I have nothing against prototyping, but the degree of
difficulty involved in engineering production code from the prototype
will ultimately determine whether anyone will use CLOS, since the objective
of most potential users is to deliver software products (unless the CLOS
is meant to be restricted to academic and research ends). Keep in mind
that I'm coming at this from a conventional architecture viewpoint.
To be specific, there are three points with which I take issue:
1) Paragraph 2, pg 1-11:
"Updating an instance does not change its identity as
defined by the EQ function"
Practically, this constrains instance implementations to use double indirect
referencing for slot access. Since most CL implementations will use pointer
equality for EQ, the requirement that instances must be updatable means that
some kind of base level storage block must be used to maintain EQ-ness, and
the slot values themselves must be stored in another, which can potentially
grow or shrink.
In fact, this requirement violates the whole concept of EQ-ness in Common
Lisp. From CLtL, pg. 78:
"Thus EQL tells whether two objects are *conceptually* the same,
whereas EQ tells whether two objects are *implementationally*
identical"
Changing the number of slots in a class, in fact, is changing the
instance implementation, since the size of the storage block
will change. Conceptually, the two classes are the same, since
they have the same name (one could argue even with this, I guess),
but clearly their instances are implemented differently, since
they have different numbers of slots.
2) The fact that instance updating can be done at all means serious
trouble for garbage collection. Since the CLOS needs somehow to
be able to find all instances of a class when the class is
incompatibly redefined, either pointers to the instances must
be kept around by the system or the entire heap must be searched
for instances when an update occurs. Presuming the latter is
a time-consuming operation (it need not be, if classes are kept in
different segments of the heap, sort of a modified BIBOP scheme,
but that has its own problems) the most efficient way would probably
be to keep track of pointers. However, if the system is holding
on to instances, they won't be forgotten when the user's code
forgets about them. This means that, without special modifications
to the garbage collector, forgotten instances will start clogging the
heap. One solution is "weak links", garbage collectable links which
the system forgets about when the user does, but these may be
difficult to implement on certain machines, or may involve much
overhead. Another is to treat user level access and update of variables
involving instances specially, so that the CLOS is notified when
the user forgets about something, but that would be putting overhead
into a process which should be fast. Finally, the user could be
required to notify the system when an instance is to be forgotten,
but that somehow doesn't fit well into the Common Lisp scheme
of things.
3) What happens to methods written on the old class? Do they
stop working? What if the methods contain a WITH-SLOTS and
some of the slots accessed within the scope of the WITH-SLOTS
disappear? Automatic instance updating would limit the amount
of optimization that could be done during a WITH-SLOTS expansion,
since changing the number of slots could invalidate any attempts
to directly access the slot values (like, for example, trying
to get rid of the double indirect referencing mentioned above).
What about accessor functions? Are they undefined? Redefined
to give an error?
4) Automatic instance updating, in and of itself, could have
potentially serious side effects in every day use.
Consider the following scenario. I am developing an application
within a programming environment written in CLOS. The environment
contains a class called TEXT-BUFFER, with a slot called CONTENTS.
While developing, I define a new TEXT-BUFFER class, but either
accidently or intentionally forget to include the CONTENTS slot.
As soon as I redefine the class, my environment is trashed, since
all the TEXT-BUFFER instances are redefined without CONTENTS.
Counter arguments to this are either "use the package system" or
"use another name for the class". But anyone who has developed
a large Common Lisp application can testify that packages and
naming are enough of a problem without adding this degree of
complexity. The potential for unpleasent suprises should cause
one to seriously reconsider automatic redefiniton, I think.
5) What, precisely, are the compile time semantics for class
changing (in fact, the entire document is vague on this subject
but more later)? If a class is being recompiled, is the
class redefined in the compile time environment? This could
potentially cause the compiler to break, if instances needed
for the compilation are updated automatically.
I would be particularly interested in hearing Garbial's comments
on this, since some of the problems I've outlined may be peculiar
to conventional architectures.
B) COMPILATION SEMANTICS
As mentioned above, there is no discussion of compilation semantics
in the document. There is a hook mentioned in 87-003 about a
compile time hash table for storing name-class associations during
compilation, but it is not clear in 87-002 what, if any, effect
this hook has on the default CLOS programmer interface. A whole
host of questions arise. Is it possible to define a class
and subclasses which inherit from it in a file and have things
work correctly? Are classes fully defined at compile time, and,
if so, will that cause the compilation environment to change
in a significant way? Can methods on a class be defined in
the same file as the class definition? Can methods on a
superclass be defined in the same file as methods on a
subclass and have optimizations for CALL-NEXT-METHOD work
correctly?
I should note that in several other object systems, making
inheritence work gracefully across seperate compilation
has been one of the hardest things to do. Objective-C,
for example, uses two text files to record inheritence
information, and this makes system building more complex.
C++ deliberately restricts how methods can be inherited
in order to avoid problems with seperate compilation.
In general, my feeling on this is that the CLOS should try
to stick to the Common Lisp goal of having the semantics
of compiled and interpreted code be identical. Additionally,
many complications can be avoided by side effecting the
compile time environment only when necessary. For example,
macros need to be defined in the compile time environment
so they get properly expanded into code being compiled,
so macro definition would seem to be a necessary side effect.
If users really want to have the compilation environment
side effected, they can always use (EVAL-WHEN (COMPILE) ...)
around the code to establish *compile time too* mode.
C) INTERACTION BETWEEN CLOS AND CL `DECLARE' AND `THE'
There is no mention in the document of how the CLOS will interact
with CL DECLARE and THE. The requirement that parameter specifiers
be type specifiers (pg. 1-19, paragraph 6) means that TYPEP
must do the right thing when given an instance object and
a valid class name (and presumably TYPEOF must return the class
name as well?) but does that mean I can say:
(DECLARE (TYPE TEXT-BUFFER X))
and:
(THE TEXT-BUFFER X)
CLtL is vague on what happens if I try to bind X to something
not of TYPE TEXT-BUFFER and I have it so DECLAREd (see pg. 158)
but more explicit for THE (pg. 161). And what happens if
I have two TEXT-BUFFER classes, one whose metaclass is
the default and one with metaclass COMMON-OBJECTS-CLASS?
Seems to me an additional set of declarations are needed,
one, perhaps, restricting the metaclass and one for
specifying that a variable name can be bound to an
object of a particular class *or a subclass*, since
the restriction to a single class is covered by TYPE.
This would allow implementations to optimize method lookup
away at compile time to varying degrees, potentially
completely for a TYPE declaration, and partially
for subclass and metaclass declarations.
D) PARTICULAR QUIBBLES
1) Pg. 1-5, paragraph 1. A reference to 87-003 (the metaobject chapter)
would probably be good here.
2) Pg. 1-6, paragraph 6. There is no statement anywhere in this section
or the next that a class cannot be defined with two slots having
the same name. Is this valid and, if so, how is access to be distinguished?
By different accessor function names? If so, which is used in WITH-SLOTS?
3) pg 1-7 through 1-9. The description of slot options, as Moon has
stated, seems needlessly complex. During the following, keep in mind
that I am coming at inheritence from the global viewpoint that subclasses
specialize behavior of superclasses. If that is kept in mind, I think
there are a number of ways to reduce the complexity. On to particulars:
a) pg. 1-7 paragraph 8:
"characteristics of that slot involve some combination of the several
slot descriptions"
I don't understand why it is necessary to have do this. Why not
simply use the class precedence list to determine which class
has highest priority and simply have the slot options for that
one dominate. Presumably, the user wants the highest precedence
class behavior to dominate, since it is the most specialized. If
that is not the case, then the slot option would, in my opinion,
be better off left as a method. The user can then redefine it
using CALL-NEXT-METHOD to get more general behavior.
b) The description of valid values on the top of the page has some notational
ambiguity. The first paragraph uses C(j) to indicate classes, but halfway
through the description on valid values, the notation uses the
syntax: (:CLASS j) to indicate that a class shares a shared slot. Did
you really mean that the user had to put a number indicating the
location of the class in the class precedence list? I think not.
c) At the bottom of the page, do you really want the default to be
UNSUPPLIED? If so, in what package is this symbol?
d) pg. 1-8. Middle bullet. Why use (AND T(1) .. T(n) )? Again, coming
at this from the point of view that subclasses specialize behavior
of supers, shouldn't the more specialized type prevail?
e) same page, last bullet. If there is no :INITFORM, then what happens
when the user tries to access the slot and it is not initialized?
In general, I think this particular bit needs to be worked out
in tandem with initialization in general, which has yet to be
specified.
4) pg 1-13 paragraph 2
"However, it is not allowed..."
I think the usual CLtL way of saying this is "an error is signalled." Also,
in paragraph 3, it sounds to me as if STRUCTURE-CLASS is actually a metaclass
is this so? Here would be a good place to have a figure illustrating the
top level metaclass/class structure which shadows the type system. Details
can be left to 87-002.2, but a figure would help conceptualization.
5) pg. 1-14-1-16. The inheritence algorithm. I actually thought this was
one of the best parts of the document (sorry Dick). It precisely specifies
how to calculate inheritence, and avoids some of the pitfalls and nasty
side effects that other multiple inheritence schemes seem to generate
(though I can't say I've examined it in lots of detail). However, it IS
poorly explained. In particular, some heuristic explanation of the role
R and S play in the algorithm would be useful. Also, on pg. 1-14, there
is a forward reference in paragraph 3, immediately after the definition
of R, to consistency, which leave the reader wondering what that is.
Also, I think the reference to topological sorting should be toned down
some, and the algorithm should rather be outlined stepwise instead of
as a paragraph. Finally, the example is OK, but there should be
examples of inconsistency as well. The single example given is trivial.
For my taste (no pun intended), the pie examples could very well be
be replaced by something more abstract, but not all readers may feel
this way.
6) pg. 1-19 after bullets:
"Let N be a parameter specializer name and P be the corresponding
parameter specializer; if N is a class name, then P is
the class with that name; otherwise N equals P."
This is unnecessarily confusing. Try:
"The method lookup process distingushes between parameter specializer
names and parameter specializers. If N is a parameter specializer
name and also the name of a class, then the corresponding parameter
specializer P will be the class named N."
The rest of this section has similar problems.
7) General comment on method combination. It is certainly clearer now
than in the original documents, but I wish the preciseness with
which the inheritence algorithm was specified could also be true
of method combination.
8) pg. 1-26. paragraph 1. Protocol point. In the list of other object
languages, why not include CommonObjects? As far as I know, it is
the ONLY language which has actually been implemented on top of
the metaobject protocol.