Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Keene on CLOS: very good recommendation

23 views
Skip to first unread message

Jeremy H. Brown

unread,
Nov 30, 2002, 2:54:26 PM11/30/02
to
Just a quick note to say thanks to everyone who recommended Keene's
book on CLOS. It was exceptionally helpful in understanding how the
various bits of CLOS are intended to be used with one another.

Jeremy

Dave Bakhash

unread,
Nov 30, 2002, 8:48:48 PM11/30/02
to

One thing that I didn't like about Keene's book is that she suggests
writing your own make-xxx constructors which in turn call
make-instance. I think that's a really bad idea.

dave

Bruce Hoult

unread,
Nov 30, 2002, 9:42:47 PM11/30/02
to
In article <c29vg2e...@no-knife.mit.edu>,
Dave Bakhash <ca...@alum.mit.edu> wrote:

When I'm writing small concrete classes that are not subclassable I
often make an inline function with regular argument that calls make with
the appropriate keyword arguments. This parallels the standard
functions for creating lists, vectors, cons cells etc.

What is your objection? There is zero overhead (at least on the
implementation I use) and it saves typing without sacrificing anything
that I am aware of.

-- Bruce

Alain Picard

unread,
Nov 30, 2002, 11:54:35 PM11/30/02
to
Dave Bakhash <ca...@alum.mit.edu> writes:

> One thing that I didn't like about Keene's book is that she suggests
> writing your own make-xxx constructors which in turn call
> make-instance. I think that's a really bad idea.

Would you mind explaining why you think that's a bad idea?

Matt Curtin

unread,
Dec 1, 2002, 7:58:53 AM12/1/02
to
Alain Picard <apicard+die...@optushome.com.au> writes:

> > writing your own make-xxx constructors which in turn call
> > make-instance. I think that's a really bad idea.
>
> Would you mind explaining why you think that's a bad idea?

One obvious problem would be the potential for naming conflict, if you
have a structure FOO and a class FOO. Following the advice being
attributed to Kleene[1], MAKE-FOO implicitly created by the DEFSTRUCT
would conflict with your own MAKE-FOO that would call MAKE-INSTANCE.

This is probably not a big deal, if you're aware of what's happening
and have documented style issues in question (e.g., the use of MAKE-
functions that in turn call MAKE-INSTANCE) so that people who read the
code later understand what's happening. Of course, Common Lisp's
namespaces can also be employed to prevent conflicts.

My own take on it is that I don't see the win of using MAKE-
vs. MAKE-INSTANCE.


Footnotes:
[1] Haven't read it, so I wouldn't notice if someone misread the
advice.

--
Matt Curtin, CISSP, IAM, INTP. Keywords: Lisp, Unix, Internet, INFOSEC.
Founder, Interhack Corporation +1 614 545 HACK http://web.interhack.com/
Author of /Developing Trust: Online Privacy and Security/ (Apress, 2001)

Simon AndrĂ¡s

unread,
Dec 1, 2002, 9:44:45 AM12/1/02
to
Matt Curtin <cmcu...@interhack.net> writes:

> Alain Picard <apicard+die...@optushome.com.au> writes:
>
> > > writing your own make-xxx constructors which in turn call
> > > make-instance. I think that's a really bad idea.
> >
> > Would you mind explaining why you think that's a bad idea?
>
> One obvious problem would be the potential for naming conflict, if you
> have a structure FOO and a class FOO. Following the advice being
> attributed to Kleene[1], MAKE-FOO implicitly created by the DEFSTRUCT
> would conflict with your own MAKE-FOO that would call MAKE-INSTANCE.

If you have a structure FOO, you already have a (structure-)class
FOO. So you can't also have a (standard-)class FOO. The conflict has
nothing to do with MAKE-FOO.

Andras

Kenny Tilton

unread,
Dec 1, 2002, 9:30:35 AM12/1/02
to

I am not "you" <g>, but what I have heard others say (and I see their
point) is that make-xxx then probably has to be called for any subclass
of XXX, lest vital initialisation go undone. That is not necessarily the
end of the world, but I think it puts a burden on the author of any
make-xxx to be sure initialize-instance won't serve as well.

--

kenny tilton
clinisys, inc
---------------------------------------------------------------
""Well, I've wrestled with reality for thirty-five years, Doctor,
and I'm happy to state I finally won out over it.""
Elwood P. Dowd

Erik Naggum

unread,
Dec 1, 2002, 11:43:21 AM12/1/02
to
* Matt Curtin

| One obvious problem would be the potential for naming conflict, if you
| have a structure FOO and a class FOO.

That is not obvious to me, as you cannot have a structure and a class
with the same name.

--
Erik Naggum, Oslo, Norway

Act from reason, and failure makes you rethink and study harder.
Act from faith, and failure makes you blame someone and push harder.

Dave Bakhash

unread,
Dec 1, 2002, 1:47:03 PM12/1/02
to
Bruce Hoult <br...@hoult.org> writes:

> What is your objection? There is zero overhead (at least on the
> implementation I use) and it saves typing without sacrificing anything
> that I am aware of.

In the ultra-simplistic way you've chosen to you is, which is basically
to create an alias function which has a different arglist, I see no
issues. But I also see no point (i.e. value) to it. So no harm, no
foul, but also no gain.

I object to placing any semantics in the make-xxx functions because
these functions are not used in the allocation and initialization of
instances of subclasses. Only make-instance is part of the object
protocol. If a programmer sees a function called `make-foo' and decides
that he's gonna change:

(defun make-foo (a b)
(make-instance 'foo :a a :b b))

to

(defun make-foo (a b)
(let ((foo (make-instance 'foo :a a :b b)))
(push foo *my-foos*)
foo))

Then you can see where things start to break down...just subclass foo,
and create an instance of the subclass. The semantics are all not
consistent.

Again, in the context of what Bruce is doing, there's nothing wrong.
But someone intruduces some side-effect in the make-xxx functions and
you end up with bugs.

dave

Richard Krush

unread,
Dec 1, 2002, 3:52:13 PM12/1/02
to
Dave Bakhash <ca...@alum.mit.edu> writes:
>
> [snip]

>
> I object to placing any semantics in the make-xxx functions because
> these functions are not used in the allocation and initialization of
> instances of subclasses. Only make-instance is part of the object
> protocol. If a programmer sees a function called `make-foo' and decides
> that he's gonna change:
>
> (defun make-foo (a b)
> (make-instance 'foo :a a :b b))
>
> to
>
> (defun make-foo (a b)
> (let ((foo (make-instance 'foo :a a :b b)))
> (push foo *my-foos*)
> foo))
>
> Then you can see where things start to break down...just subclass foo,
> and create an instance of the subclass. The semantics are all not
> consistent.
>
> Again, in the context of what Bruce is doing, there's nothing wrong.
> But someone intruduces some side-effect in the make-xxx functions and
> you end up with bugs.
>

Could you elaborate on how it will be causing bugs? The only way I can think
of is that the subclass will not be pushed into *MY-FOOS*, but to counter
that, the author can easily create the MAKE-* wrappers for all the classes.
Excuse me if this sounds dumb, I'm still a novice and at this point am using
such wrappers quite extensively (primarily for aesthetic reasons).

Thanks in advance!

Richard

--
"I know not with what weapons World War III will be fought, but World War
IV will be fought with sticks and stones." -- Albert Einstein

Rahul Jain

unread,
Dec 1, 2002, 4:06:45 PM12/1/02
to
Richard Krush <rkr...@gmx.net> writes:

> Could you elaborate on how it will be causing bugs? The only way I can think
> of is that the subclass will not be pushed into *MY-FOOS*, but to counter
> that, the author can easily create the MAKE-* wrappers for all the classes.
> Excuse me if this sounds dumb, I'm still a novice and at this point am using
> such wrappers quite extensively (primarily for aesthetic reasons).

You have the right idea, but remember that the point of OOP is that we
can abstract these kinds of behaviors. Instead of creating a special
constructor (which could be accidentally bypassed by someone who knows
how to use the CL reflection facilities), all that is really needed is
a :after method on shared-initialize (which brings up the issue of
objects whose class is changed from FOO to BAR; those should be
removed from *MY-FOOS* and vice vesa from BAR to FOO).

--
Rahul Jain

Dave Bakhash

unread,
Dec 1, 2002, 4:48:09 PM12/1/02
to
Richard Krush <rkr...@gmx.net> writes:

> Could you elaborate on how it will be causing bugs? The only way I
> can think of is that the subclass will not be pushed into *MY-FOOS*,
> but to counter that, the author can easily create the MAKE-* wrappers
> for all the classes. Excuse me if this sounds dumb, I'm still a
> novice and at this point am using such wrappers quite extensively
> (primarily for aesthetic reasons).

First off, if you care about asthetics, then get rid of your make-xxx
functions. They add clutter with not much actual value. Your DEFCLASS
form gives you default values (:initform), keyword argument names, and
initialization logic (e.g. via :after methods on INITIALIZE-INSTANCE).

I am not claiming that you'll introduce bugs if you use this `make-xxx'
business. But you have to be pretty anal about your new protocol to
make sure you get it right, and it's just more cumbersome overhead
that's completely unnecessary. You could do it if you constrain your
make-xxx functions to be side-effect-free, kinda like the ones generated
by the :constructor option on DEFSTRUCT. Once you're staring at a
make-xxx function, it becomes dangerously obvious to add side effects
there. CL is loose enough...it becomes like a quicksand trap.

Here's a simple example of a make-xxx mistake:

(defclass person ()
((name :initarg :name
:initform (error)
:accessor name)))

(defclass programmer (person)
((language :initarg :language
:initform :common-lisp
:accessor language)))

Now, suppose that someone thought that writing:

(setq p (make-instance 'person :name "Joe Blah"))

was just too much extraneous code. Of course, they could do this:

(defun make-person (name &rest args)
(apply #'make-instance 'person :name name args))

Well, this is nice, since it's even extensible, just in case someone
adds some additional slots to the `person' class. Of course, you've
only really added another (trivial) level of indirection to constructing
your object (i.e. if anything, just slowed things down). Now let's
imagine that you want to make sure your names are nicely trimmed with
respect to whitespace on either side. so you do:

(defun make-person (name &rest args)
(apply #'make-instance 'person :name (string-trim " " name) args))

well, great...but if you do this:

(setq me (make-instance 'programmer :name (ui-get-name dialog-box)))

You're screwed if that UI function didn't trim the strings. So you
say...okay...well, no big deal. I'll just fix it:

(setq me (make-instance 'programmer :name (string-trim " " (ui-get-name dialog-box))))

But why should I have to do that? I already put that logic in my
make-person function, right? So maybe I should do this:

(defun make-programmer (name language &rest args)
(let ((person (make-person name)))
(apply #'change-class person 'programmer :language language args)
person))

Is that what you want? Isn't that just nasty? And all this because you
want some make-xxx functions? Just think about it. This is not even a
nasty example. It gets even more nasty. It's a bad idea. I would
suggest leaving it alone.

dave

Jeremy H. Brown

unread,
Dec 2, 2002, 1:22:40 AM12/2/02
to
Matt Curtin <cmcu...@interhack.net> writes:
> Following the advice being attributed to Kleene[1]...
[clip]

> Footnotes:
> [1] Haven't read it, so I wouldn't notice if someone misread the
> advice.

As a point of information, here's the entirety (a whopping three
paragraphs) of Section 9.6 from Keene's book:

------------------------------------------------------------
9.6 CONSTRUCTORS

We recommend using constructors as the external interface for creating
instances, because constructors add a valuable level of abstraction
between the client and the implementation. Consider triangles: The
name of the constructor, make-triangle, implies "making a triangle,"
which is a higher-level concept than is "making an instance of the
triangle class."

Another advantage of constructors is that they can use the full power
of Common Lisp argument-processing. The make-instance syntax is
extremely limited: Following the first argument (the class) is an
&rest parameter consisting of initargs. In many cases, the semantics
of a class can be better expressed with required arguments, optional
arguments, and so on. With triangles, for example, the &rest argument
to make-instance fails to imply that all three initargs --- the sides
--- are required to make a triangle. The constructor, however, can
make the three sides be required arguments; the syntax of the
constructor accurately reflects the semantics of triangles.

Perhaps most important, constructors conceal the implementation of
objects, which frees you to change the implementation without
disturbing client programs. If you advertise constructors as the
external interface, you can later change to a defstruct representation
of the object or change the name or initargs of the class, without
invalidating client programs. Constructors can also select one of
several classes, based on its arguments. If you advertise
make-instance as the external interface, you cannot make these changes
within the implementation.
------------------------------------------------------------

Jeremy

Alain Picard

unread,
Dec 1, 2002, 4:42:20 PM12/1/02
to
Dave Bakhash <ca...@alum.mit.edu> writes:

> I object to placing any semantics in the make-xxx functions because
> these functions are not used in the allocation and initialization of
> instances of subclasses.

> Only make-instance is part of the object protocol.

Ah. I see; yes, that's a good objection. A "weak" rebuttal
would be that providing MAKE-XXX functions essesntially
publishes what you think are the instantiable classes.

It seems to me if your classes provide any sort of framework,
where you don't know where classes will be subclassed by the
end programmer, your objection is very serious indeed.

Thanks for reminding me of this important point (about the
creation/customization protocol).

Bruce Hoult

unread,
Dec 2, 2002, 2:49:44 AM12/2/02
to
In article <uv61y51...@tenebrae.ai.mit.edu>,

jhb...@ai.mit.edu (Jeremy H. Brown) wrote:

> Matt Curtin <cmcu...@interhack.net> writes:
> > Following the advice being attributed to Kleene[1]...
> [clip]
> > Footnotes:
> > [1] Haven't read it, so I wouldn't notice if someone misread the
> > advice.
>
> As a point of information, here's the entirety (a whopping three
> paragraphs) of Section 9.6 from Keene's book:
>
> ------------------------------------------------------------
> 9.6 CONSTRUCTORS
>
> We recommend using constructors as the external interface for creating
> instances, because constructors add a valuable level of abstraction
> between the client and the implementation. Consider triangles: The
> name of the constructor, make-triangle, implies "making a triangle,"
> which is a higher-level concept than is "making an instance of the
> triangle class."

Although I like using constructors for simple concrete classes, I find
it interesting that my reasons in Dylan are totally disjoint from
Keene's reasons in Common Lisp. In Dylan I use constructors mostly when
giving a keyword for every slot initializer seems like overkill because
the class is simple enough that ordering alone is sufficient.

It seems to be the case that Keene's reasons all apply only to Common
Lisp and not to Dylan.


> Another advantage of constructors is that they can use the full power
> of Common Lisp argument-processing. The make-instance syntax is
> extremely limited: Following the first argument (the class) is an
> &rest parameter consisting of initargs. In many cases, the semantics
> of a class can be better expressed with required arguments, optional
> arguments, and so on.

Dylan lets you add methods to either or both of make() and initialize(),
which can do fairly arbitrary rearrangement of the arguments to make().
The main limitation is that they have to be keyword arguments, not
positional.


> With triangles, for example, the &rest argument
> to make-instance fails to imply that all three initargs --- the sides
> --- are required to make a triangle. The constructor, however, can
> make the three sides be required arguments; the syntax of the
> constructor accurately reflects the semantics of triangles.

In Dylan you'd just put a required-init-keyword specifier on each slot
declaration.


> Perhaps most important, constructors conceal the implementation of
> objects, which frees you to change the implementation without
> disturbing client programs. If you advertise constructors as the
> external interface, you can later change to a defstruct representation
> of the object or change the name or initargs of the class, without
> invalidating client programs.

Dylan's initialize() methods can accept keyword arguments that (no
longer?) name slots, and use them to initialize the replacement slots.


> Constructors can also select one of
> several classes, based on its arguments. If you advertise
> make-instance as the external interface, you cannot make these changes
> within the implementation.

This is the situation in which you add a method to the make() GF in
Dylan. You can then examine the arguments and choose to alter them, or
return an existing object instead of a new one, or even make an object
of a different class and return it instead. This is even done in the
standard library, so that you can call make() on various abstract
classes, and it instead returns a suitable default concrete
implementation e.g. make(<vector>) returns a <simple-object-vector> and
make(<string>) returns a <byte-string>.

-- Bruce

Dave Bakhash

unread,
Dec 2, 2002, 11:03:59 AM12/2/02
to
jhb...@ai.mit.edu (Jeremy H. Brown) writes:

> As a point of information, here's the entirety
> (a whopping three paragraphs) of Section 9.6 from Keene's book

^ ^^^^^^^^ ^^^^^ ^^^^^^^^^^
Jeremy,

If you re-read my post, you'll see that I wasn't dismissing the whole
book, but pointing out a single case of where I personally disagreed
with the programming philosophy the author was promoting.

Beyond that, I would argue that it's not how many paragraphs were
devoted to promoting it. That's irrelevant. Extremely significant
statements can be made very succinctly. An inexperienced reader could
easily get caught up in Keene's ``whopping'' three paragraphs.

Bruce Hoult <br...@hoult.org> writes:

> > With triangles, for example, the &rest argument to make-instance
> > fails to imply that all three initargs --- the sides --- are
> > required to make a triangle. The constructor, however, can make the
> > three sides be required arguments; the syntax of the constructor
> > accurately reflects the semantics of triangles.
>
> In Dylan you'd just put a required-init-keyword specifier on each slot
> declaration.

Well, you can easily enforce required arguments through the :initform,
which is what CLX does. If you read the sources for CLX, you see things
like this:

(defclass my-class ()
((my-slot :accessor my-slot
:initarg :my-slot
:initform (required-arg my-slot))))

where the `required-arg' is a macro which generates an appropriate
error.

It's not perfect, but it gets the job done, if that's what you want.

I believe that you can add slot options too, if that's really what you
want, using the MOP, though it probably wouldn't be worthwhile for
something so minor.

> > Perhaps most important, constructors conceal the implementation of
> > objects, which frees you to change the implementation without
> > disturbing client programs. If you advertise constructors as the
> > external interface, you can later change to a defstruct
> > representation of the object or change the name or initargs of the
> > class, without invalidating client programs.

This is a good point. However, if you can live with the MAKE-INSTANCE
keyword-based syntax (which is, in my opinion, most general, and very
flexible), then you can define a MAKE-INSTANCE method for
structure-class as well:

(defmethod make-instance ((class structure-class) &rest initargs)
(apply (structure:constructor class) initargs))

Of course, you'll have to look inside your implementation to see if the
`structure:constructor' function even exists, or if you can somehow get
hold of it. (It's possible that you can't...I havn't looked into it).
But if it's not, it's not much to ask a vendor for.

dave


Jeremy H. Brown

unread,
Dec 2, 2002, 1:00:34 PM12/2/02
to
Dave Bakhash <ca...@alum.mit.edu> writes:
> jhb...@ai.mit.edu (Jeremy H. Brown) writes:
>
> > As a point of information, here's the entirety
> > (a whopping three paragraphs) of Section 9.6 from Keene's book
> ^ ^^^^^^^^ ^^^^^ ^^^^^^^^^^
> Jeremy,
>
> If you re-read my post, you'll see that I wasn't dismissing the whole
> book, but pointing out a single case of where I personally disagreed
> with the programming philosophy the author was promoting.
>
> Beyond that, I would argue that it's not how many paragraphs were
> devoted to promoting it. That's irrelevant. Extremely significant
> statements can be made very succinctly. An inexperienced reader could
> easily get caught up in Keene's ``whopping'' three paragraphs.

I wasn't looking to have a fight about the size of the doctrinal
passage, I was making an oblique appeal to fair use with respect to my
posting an entire section of the book --- an act which is only fair
use because the section is short.

For all that the explicit salesmanship is in these three paragraphs,
the style advocated is used throughout the book.

Jeremy

Thomas F. Burdick

unread,
Dec 2, 2002, 2:16:17 PM12/2/02
to
jhb...@ai.mit.edu (Jeremy H. Brown) writes:

> For all that the explicit salesmanship is in these three paragraphs,
> the style advocated is used throughout the book.

Out of curiosity, does she use ordinary functions for constructors, or
generic functions?

--
/|_ .-----------------------.
,' .\ / | No to Imperialist war |
,--' _,' | Wage class war! |
/ / `-----------------------'
( -. |
| ) |
(`-. '--.)
`. )----'

Thomas F. Burdick

unread,
Dec 2, 2002, 2:28:24 PM12/2/02
to
Alain Picard <apicard+die...@optushome.com.au> writes:

> Dave Bakhash <ca...@alum.mit.edu> writes:
>
> > I object to placing any semantics in the make-xxx functions because
> > these functions are not used in the allocation and initialization of
> > instances of subclasses.
>
> > Only make-instance is part of the object protocol.
>
> Ah. I see; yes, that's a good objection. A "weak" rebuttal
> would be that providing MAKE-XXX functions essesntially
> publishes what you think are the instantiable classes.

That is a weak rebuttal :-). I think a better way of publishing
information like that would be to use the package system and naming
conventions. For example, if I were using your FOO module, and I
wrote this code:

(let ((obj (make-instance 'foo:some-mixin ...))) ...)

I would expect to lose. For intramodule consistency, I've been known
to use a DEFMIXIN macro that's a thin wrapper over DEFCLASS, with the
option of working in debugging mode, where it raises a continuable
error if the class is ever instantiated directly. I've found it
useful for making sure the left hand knows what the right hand is
doing.

> It seems to me if your classes provide any sort of framework,
> where you don't know where classes will be subclassed by the
> end programmer, your objection is very serious indeed.

I'm not a fan of constructor functions, but factory functions are just
fine by me. It seems like using a constructor function that will only
ever return objects of a single class is the moral equivalent of using
your own LINKED-LIST class instead of just using conses.

Dave Bakhash

unread,
Dec 2, 2002, 2:43:03 PM12/2/02
to
t...@avalanche.OCF.Berkeley.EDU (Thomas F. Burdick) writes:

> Out of curiosity, does she use ordinary functions for constructors, or
> generic functions?

While unable to remember that level of detail, I would say that
regardless of what she used...you're only compounding the problem if you
make the constructors generic.

dave

Jeremy H. Brown

unread,
Dec 2, 2002, 7:39:45 PM12/2/02
to
t...@avalanche.OCF.Berkeley.EDU (Thomas F. Burdick) writes:

> jhb...@ai.mit.edu (Jeremy H. Brown) writes:
>
> > For all that the explicit salesmanship is in these three paragraphs,
> > the style advocated is used throughout the book.
>
> Out of curiosity, does she use ordinary functions for constructors, or
> generic functions?

At a quick glance, looks like she sticks with ordinary functions, e.g.

(defun make-triangle (a b c) ...

on page 150, in section 8.3.

Jeremy

Christopher Browne

unread,
Dec 2, 2002, 8:11:36 PM12/2/02
to
In an attempt to throw the authorities off his trail, Dave Bakhash <ca...@alum.mit.edu> transmitted:

If memory serves, she presents the constructors as simple functions.

It would probably be worth having some observation there to the effect
that:

"These are simple constructors, intended only for use with the
specific class presented. They provide a convenient shorthand, but
are not intended as generic constructors..."
--
(reverse (concatenate 'string "gro.gultn@" "enworbbc"))
http://www3.sympatico.ca/cbbrowne/lisp.html
Introducing "lite", the new way to spell "light", with 20% fewer
letters!

0 new messages