[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Constructors
This message contains my comments about constructors. I wrote this
quickly, but I have been thinking about it for a while. This means that
this may sound a little rough or confused, but I believe pretty firmly
what I am trying to say. If there is a part that seems confusing give
it another read. I won't be reading any more mail until after OOPSLA,
so I won't be able to respond to responses to this till then.
Date: Wed, 30 Sep 87 12:37 EDT
From: David A. Moon <Moon@STONY-BROOK.SCRC.Symbolics.COM>
This is a proposal to add constructors back into CLOS, now that the basic
object creation protocol has been agreed upon.
WHY HAVE CONSTRUCTORS?
(1) A cleaner interface for the caller
It's often appropriate to have a more abstract interface than the one provided
by MAKE-INSTANCE. Providing a constructor as the documented inter-module
interface for making a particular kind of object encourages users of the
interface to think in more abstract, conceptual terms. Using a constructor
also allows more aspects of the implementation to be changed without changing
the interface: the class name and initarg names could be changed, or the data
representation could be changed to a DEFSTRUCT representation or a
standard-type, without changing the interface. The constructor could even be
replaced by an interface function that does some complex computations to
decide what type of object to create, or to decide whether to return an
existing object or create a new one.
It is true that sometimes, constructors present a more appropriate
interface for the caller. On the other hand, it is often the case that
they are not a more appropriate interface for the caller since if the
caller decides to make a intance of a slightly more specialized version
of something they must either define a new constructor or resort to
calling make-instance.
Also, I believe that much of the functionality you say can be gotten
with constructors can be gotten just as well with make-instance. By
using individual methods on make-instance, it would be easy for a
programmer to have code that for a particular class "does some complex
computations to decide what type of object to create, or to decide
whether to return an existing object or create a new one". Some might
argue that individual methods on make-instance are bad style, (I don't
know what I think) but I believe that argument would be an equally
strong argument against that kind of constructor function.
Also, if we decide to do structure-class (I assume we will) the
programmer could change to a defstruct and still use code that calls
make-instance.
Also, while it is true that these are reasonable arguments for the use
of constructors as an interface to the caller, they are not arguments
for why there should be a :constructor option. I understand that you go
on to acknowledge that, but I wanted to make it clear.
These needs could be met simply by defining constructor functions
with DEFUN and advertising them. Some reasons to have a
:CONSTRUCTOR option in DEFCLASS are
- to make it easier and more convenient for users to create
constructors
As Danny says, I believe this sacrifices reading ease in favor of
typing ease in a serious way.
- to be culturally consistent with DEFSTRUCT
I agree with Danny about this too.
- :CONSTRUCTOR is a convenient abbreviation for something you could
do yourself in a more long-winded way, just like :ACCESSOR
Sort of, but :accessor is a more trivial case. But perhaps we should get
rid of :accessor? (Later in this message I make more comments about
:accessor.)
Other reasons appear below.
(2) Coordination with class redefinition
As the bridge between an external interface and the internal structure
of a class, a constructor function contains certain information about
the class, such as what are its initargs and their default values. When
a class is redefined, any constructors for the class and its subclasses
should be updated if necessary to keep them consistent with the class.
Making this updating automatic is a convenience for programmers, so they
don't have to remember to do it by hand. One way to make the updating
automatic would be to add a new feature to the programming environment
by which a linkage could be established between a function and a class
so that redefining the class makes some edits to the source of the
function and then recompiles it. A much simpler way is to make the
constructor syntactically part of the DEFCLASS, so it naturally gets
updated at the same time as the rest of the DEFCLASS. This is another
way in which constructors are analogous to accessors.
I believe this is the strongest argument I have heard for constructors.
But lets look at it for a minute.
- Lets suppose that someone is defining boa constructors by hand (using
defun). Presumably their reason for doing this is that they want to
present a boa interface to a subset of the total initargs the function
accepts. In that case, changes to the class lattice which affect the
initargs the embedded call to make-instance accepts can have one of two
effects:
1: They don't change the initargs the constructor is presenting
a boa interface to. In this case the original constructor
defun is more than likely still semantically and syntactically
correct.
2: They do affect the initargs the constructor is presenting a boa
interface to. In this case the constructor defun is going to have
to be examined and perhaps edited. But in this case what would
having a :constructor option to defclass have bought the user?
Nothing as near as I can tell, they will still have to examine
that :constructor option and perhaps edit it.
The point being that in general, if there is a change which affects the
initargs a constructor is providing a boa interface to the programmer is
going to have to examine the constructor to make sure it is still valid.
Now lets look at the case where the constructor is not providing a boa
interface but rather is providing just a initarg interface like make
instance. In this case the constructor looks like:
(defun make-foo (&rest args)
(apply #'make-instance 'foo args))
Well in this case the code defined with defun will always be valid under
changes to the class lattice, only the code that actually calls make-foo
can become incorrect and so once again I see no real benefit being
provided by the :constructor option.
(3) More efficient than MAKE-INSTANCE
Gregor has argued that calls to MAKE-INSTANCE with a constant first
argument can be equally optimized, since the exact class being
constructed is known. While this is true in theory, it seems that
either a complicated mechanism would be needed to make sure that the
function was recompiled when the class was redefined in a way that
invalidated the inline code, or else there would have to be user-visible
declarations to control the tradeoff between performance and robustness
in the face of class redefinition. Alternatively, calls to
MAKE-INSTANCE with a constant first argument could be turned into calls
to a constructor function that was created behind the scenes. Then if
the class was redefined, only the constructor function would have to be
recompiled. In either case, if we are going to have this type of
mechanism, I would much rather make it explicitly visible as a
:CONSTRUCTOR option than have it operating behind the scenes in some
vaguely defined way.
What you describe as the alternative is the only way I have thought of
doing it and I don't in any way see why this is harder to implement that
the mechanism you are proposing. I do believe I have show the two be be
of equivalent difficulty to implement.
Also, I don't really believe semantic preserving optimizations need to
be made explicit. That is the opposite of what a (safe) compiler
optimization means to me.
Whats more, if we explicitly document that :constructors are likely to
be faster than make-instance we will be encouraging users to use
constructors all the time rather than make-instance and I think that
would be bad bad bad.
WHY GET RID OF CONSTRUCTORS?
(1) Simplicity
If we have both constructors and MAKE-INSTANCE, then we have two ways to
do the same thing.
[But CLOS very often provides both a primitive mechanism and a convenient
abbreviation for a common case of using that mechanism.]
The rules for mapping constructor parameter names into slots and initargs are
complicated and confusing.
[Very true. In this proposal they have been enormously
simplified.]
I must confess that the rules you propose here are simpler than those
before.
(2) Avoid hiding mechanisms
Constructors contain a hidden performance optimization, in that there is
more inlining in their bodies than can be achieved through documented
mechanisms elsewhere.
[I argued above that this is preferable to the inherent complexity of
making that mechanism generally available. Of course there is nothing
to stop us from documenting it if that's what we really want.
Also, exactly the same thing could be said about :ACCESSOR, at least
in the Symbolics implementation.]
See my comment above about how bad it would be to explicitly say that
constructors are faster than make-instance.
Also note that it would be trivial to teach your implementation (and
mine) to reckognize the following and optimize it the same way as
accessors:
(defmethod foo-x ((foo foo)) (slot-value foo 'x))
(3) Not more efficient than MAKE-INSTANCE
(see discussion above)
I have read carefully the rest of the message which contains the details
of the proposal, but am not going to comment on those details here other
than to say that as a proposal for constructors they seem reasonable.
Summary:
I am still opposed to adding constructors to CLOS. I do believe that
they will make life a little more convenient for some programmers, but I
don't think those advantages outweigh the conceptual problems I outline
above nor do I think they are worth the added complexity of having to
describe and understand how they work and documenting the mechanism
which is used to compile them and cause them to get recompiled when
appropriate.
-------