From: Howard R. Stearns
Subject: New initforms for inherited slots vs simple-condition-format-control
Date: 
Message-ID: <37053B55.CFC7ECA@elwood.com>
Consider this example:

(define-condition FILE-NOT-FOUND-ERROR (file-error) ())
(define-condition SIMPLE-FILE-NOT-FOUND-ERROR (file-not-found-error
					       simple-condition)
  ((format-control :initform "~a was not found."
		   :reader SIMPLE-CONDITION-FORMAT-CONTROL)))

Format-control is a slot in every implementation of simple-condition,
but no one is allowed to know the name of that slot.  So, if you want
to provide a default initform, you must define the
SIMPLE-CONDITION-FORMAT-CONTROL reader.  You then have to hope that
your Lisp implementation does the right thing and uses this function
to extract the value during condition reporting, instead of using
SLOT-VALUE, WITH-SLOTS, or some internal mechanism.

OK, I don't have any problem with that.  BUT....

SIMPLE-CONDITION-FORMAT-CONTROL is one of a whole slew of functions
which ANSI defines only as FUNCTIONS, not as GENERIC-FUNCTIONS.  Thus,
I can't legally define this reader method.

Indeed, in CMUCL, (typep #'simple-condition-format-control
'generic-function) => NIL.  [(class-of
#'simple-condition-format-control) =>
#<KERNEL:FUNCALLABLE-STRUCTURE-CLASS KERNEL:BYTE-CLOSURE (sealed)
{5022D9D}>.]  (You have no idea how difficult it was to figure out
that defining this method on simple-condition-format-control was the
cause of my system crashing every time the compiler tried to warn me
about anything!) 

The only work-around I can think of is to define:

(defun SIGNAL-FILE-NOT-FOUND-ERROR (pathname)
  (error 'simple-file-not-found-error :pathname pathname
	 :format-control "~a was not found."
	 :format-arguments (list pathname)))

....and always use this function to signal the error instead of calling
ERROR directly.  (I kind of have to do this anyway to get around a
related but even harder issue: how can I make
SIMPLE-CONDITION-FORMAT-ARGUMENTS return a list of the
FILE-ERROR-PATHNAME argument automatically, without having to fill it in
each time I call (ERROR 'SIMPLE-FILE-NOT-FOUND-ERROR :pathname x).)

So I have two questions:
1. Is there a different work around that I'm not seeing?
2. Does this suggest a problem in the language?  Ex.:
   A. A lot more things needs to be specified as generic-functions,
      which must then also specifically documented to be called 
      internally by the implementation in the right places. Or...
   B. This idea of keeping slot names private isn't really working 
      out.

From: Kent M Pitman
Subject: Re: New initforms for inherited slots vs simple-condition-format-control
Date: 
Message-ID: <sfw7lruw3vc.fsf@world.std.com>
"Howard R. Stearns" <······@elwood.com> writes:

> SIMPLE-CONDITION-FORMAT-CONTROL is one of a whole slew of functions
> which ANSI defines only as FUNCTIONS, not as GENERIC-FUNCTIONS.  Thus,
> I can't legally define this reader method.

Right.

Some people on the committee insisted they wanted the ability to deploy
a CLOS-free subset that still had conditions.  It was a design criterion
that the condition system not require you to use generic functions.

Understand that although X3J13 is a democratic body, the function of
a standards committee is also to avoid anti-trust action by some vendors
against others if votes are taken that impose an unnecessary burden on
one vendor.  The cost of this accomodation was at the time small, and
I would argue still is.  If we were ever able, we might change it, but
mostly because I don't think there are still any Lisps that insist on it.

Incidentally, you're not required to make SIMPLE-CONDITION-xxx
be non-generics.  When it says "Function" it means "this might be a generic
function in any given implementation, but you cannot depend on it portably".
It does not mean "implementations are forbidden from making this
be a generic function".  It also doesn't mean "Implementors are required
to tell users they cannot extend the definition of the generic function
by adding methods."  It only means "Programs that purport to
be portable would be ill-advised to assume that an extension offered in
one implementation will be present in others and therefore must not claim
conformance if they avaib themselves of such special features."

> 
> Indeed, in CMUCL, (typep #'simple-condition-format-control
> 'generic-function) => NIL.  [(class-of
> #'simple-condition-format-control) =>
> #<KERNEL:FUNCALLABLE-STRUCTURE-CLASS KERNEL:BYTE-CLOSURE (sealed)
> {5022D9D}>.]  (You have no idea how difficult it was to figure out
> that defining this method on simple-condition-format-control was the
> cause of my system crashing every time the compiler tried to warn me
> about anything!) 
> 
> The only work-around I can think of is to define:
> 
> (defun SIGNAL-FILE-NOT-FOUND-ERROR (pathname)
>   (error 'simple-file-not-found-error :pathname pathname
> 	 :format-control "~a was not found."
> 	 :format-arguments (list pathname)))
> 
> ....and always use this function to signal the error instead of calling
> ERROR directly.  (I kind of have to do this anyway to get around a
> related but even harder issue: how can I make
> SIMPLE-CONDITION-FORMAT-ARGUMENTS return a list of the
> FILE-ERROR-PATHNAME argument automatically, without having to fill it in
> each time I call (ERROR 'SIMPLE-FILE-NOT-FOUND-ERROR :pathname x).)
>
> So I have two questions:
> 1. Is there a different work around that I'm not seeing?

Well, you could also just do this call to ERROR every time explicitly
instead of defining a function. :-)

> 2. Does this suggest a problem in the language?  Ex.:
>    A. A lot more things needs to be specified as generic-functions,
>       which must then also specifically documented to be called 
>       internally by the implementation in the right places. Or...

There were very specific reasons for making this one not generic but
there are other reasons why others are not generic that you might wish
for.  It is reasonable and appropriate to review each, but I would
not advocate a blanket vote ...

>    B. This idea of keeping slot names private isn't really working 
>       out.

I don't see what slot names have to do with it.  Beside everything else,
the metaclass of a condition is not specified, so slot accesses are
probably not conforming anyway.
From: Howard R. Stearns
Subject: Re: New initforms for inherited slots vs simple-condition-format-control
Date: 
Message-ID: <3708C079.8F026301@elwood.com>
Kent M Pitman wrote:
>
> >    B. This idea of keeping slot names private isn't really working
> >       out.
> 
> I don't see what slot names have to do with it.  Beside everything else,
> the metaclass of a condition is not specified, so slot accesses are
> probably not conforming anyway.

You're right that slot accesses are not conforming.  It seems to me that
there are a couple of ways in which one could build up a kind of "open
implmentation" of something like simple-condition reporting:

1. The all-GF way.  All the relevent utilities would be documented to
use various generic functions internally, and these generic functions
would be documented as being overridable by programmers (i.e., we are
allowed to write overriding methods specialized on our own classes). 
This is what the MOP generally does, and, in a somewhat hand-waving way,
is what people often cite about Smalltalk as a strength. 

On problem here is that as far as I can tell, it pretty much bars anyone
from using slot-value or with-slots.  Maybe that's not a bad thing. 
However, there are some activities where you might want to grovel over
available slots (where the set of slots is computed at run time from
metadata) and get some of their values from an instance.  I don't think
there is anyway of knowing whether these values will be coordinated with
specialized accessor methods.  (I suppose you could check applicable
methods and see if these methods are of class standard-accessor method,
but just because they are not, it doesn't NECESARRILLY mean that
slot-value will give an inappropriate answer.)

2. Admit that values are stored in slots and document the name of the
slot. This allows slot-value, etc., to be used.  This is probably
putting too much constraint on both the implementation and the
programmer, and I'm not sure it's a good idea unless its necessarry, but
I wonder if the issues raised on (1) lean towards it being necessarry.

In my original example, I showed that one might want to provide a new
:initform for a slot that you "know" must be defined by the
implementation, but you just don't know it's name.  One argument is that
this is exactly the way that new :initforms are supposed to be provided,
and the other side of the argument is that the whole reason these slot
names aren't defined is that a programmer shouldn't rely on these things
comming from slots at all. It's just the flip side of the argument
against (1).

Another example I hinted at is where you want to initialize some slot
based on the values of other slots.  Here one might want to define some
shared-initialize :after method which examined some existing slots. 
Because this method might need to check for slot-boundp, etc., it needs
to know the names of the slots.  One alternative would be to map over
the (class-slots (class-of instance)), but now you're really going into
the implementation, and might have to sort through all kinds of slots
that you have no business looking at.   (Perhaps I could look to see
which slot has an accessor that matches a particular name, but remember,
the whole argument is that if it's not documented, there might not even
be such a slot, or the accessor might not be so obviously/directly tied
to it.)

I don't know what the right answer is.  I don't want to give myself too
much rope, but I do want enough that I can do what I want -- in this
case, providing initial values which will be used by some documented
accessors.
From: Kent M Pitman
Subject: Re: New initforms for inherited slots vs simple-condition-format-control
Date: 
Message-ID: <sfwiub56lyn.fsf@world.std.com>
"Howard R. Stearns" <······@elwood.com> writes:

> You're right that slot accesses [to conditions] are not conforming.
> It seems to me that
> there are a couple of ways in which one could build up a kind of "open
> implmentation" of something like simple-condition reporting: 
> 1. The all-GF way.  All the relevent utilities would be documented to
> use various generic functions internally, [...]
> On problem here is that as far as I can tell, it pretty much bars anyone
> from using slot-value or with-slots.

Which is good because since these are read-only slots, you don't have
to explain why SETQ doesn't work.  With WITH-SLOTS, you ordinarily
have SETQ.  And we specifically disallowed assignment of condition
slots, and for good reason.  With methods for getting/setting, it's
easy to have a getter without a setter, but with variables there are
no "read-only" variables.  There is no "constant" declaration.  Probably
there should be, but...

> ... there are some activities where you might want to grovel over
> available slots (where the set of slots is computed at run time from
> metadata) and get some of their values from an instance.

I'd like to see such a case before opining, but my inclination is to
think these are not very good cases.

> 2. Admit that values are stored in slots and document the name of the
> slot.

The reason these are restricted isn't that they are not stored in
slots.  It is multi-fold and orthogonal.  The structure-class has
slots, but they are not the standard-class style of slots; that's a
representational issue that illustrates the kind of variation--error
conditions might be structs or even something different.  But
additionally, the reason that condition slots aren't assignable is
that it's very poor style to assign them.  (Some concerns were raised
about how to initialize instances properly without some very
restricted form of assignment one can use in initialize-instance, and
I have some sympathy for this problem, but a lot of people want to
assign slots after the signal has begun and I'm pretty hard-line that
I can only think of bad reasons to do that and very few good ones.  A
condition represents a static point in time and really should not
represent an evolving understanding because although
implementationally there is an ordering on condition handlers,
abstractly the order in which condition handlers are sought is
arbitrary in that one is not supposed to write handlers that
presuppose the existence of other handlers, and so if your handlers
behave differently depending on whether another handler has changed
the shape of a condition, your handler is unreliable and ill-defined
in a way that I think is poor.  (This is Kent's personal subjective
judgment creeping in here; but at the time the decision was made,
others agreed with me and there has been no complaint since then that
this was a bad plan.)  Having slots not assignable also enables a very
important feature of conditions which is "reuse".  Consider a storage
condition--it might be hard to cons if you were out of storage, and
it's useful to have it preconsed, and perhaps even to use it more than
once.  Any commonly signaled condition is handy to have pre-consed and
reusable, in fact.  But if someone can assign it, that thwarts the
ability to have reuse.  Think of an unmodifiable condition that is
pre-consed as the object-equivalent of a pure function (i.e., with no
state); that is, you might get the self-same object each time the equivalent
condition occurs (assuming it doesn't occur nested, which requires a
different object since two ongoing signals need distinct objects) 
just as a function with no state can yield the same each time.
From: Howard R. Stearns
Subject: Re: New initforms for inherited slots vs simple-condition-format-control
Date: 
Message-ID: <3711FBD1.4008E0EC@elwood.com>
Kent M Pitman wrote:
> 
> "Howard R. Stearns" <······@elwood.com> writes:
> > ... there are some activities where you might want to grovel over
> > available slots (where the set of slots is computed at run time from
> > metadata) and get some of their values from an instance.
> 
> I'd like to see such a case before opining, but my inclination is to
> think these are not very good cases.

See below.

> ...[T]he reason that condition slots aren't assignable is
> that it's very poor style to assign them.  (Some concerns were raised
> about how to initialize instances properly without some very
> restricted form of assignment one can use in initialize-instance, and
> I have some sympathy for this problem, ... [Several on-point comments 
> specific to condition classes.]

Here's the extended version of the code which triggered all this. 
AUTO-ARGUMENTS-ERROR is a kind of simple-error in which the
format-arguments can be :initarg'ed in the usual way, but otherwise is
built automatically by listing all the other "relevent" values.  There
are then some example conditions built on this which the format-control
also has an :initarg'able default.  In use, one could just do (error
'end-of-file :stream xxxx) and have all the right things happen, but one
could also do other combinations such as (error 'end-of-file :stream xxx
:format-control "Bad ~a.")

(define-condition AUTO-ARGUMENTS-ERROR (simple-error)
  ((format-arguments :initform 'not-initialized)))

(defmethod initialize-instance :after ((condition AUTO-ARGUMENTS-ERROR)
				       &rest initargs)
  (declare (ignore initargs))
  (let ((class (class-of condition)))
    (when (eq (slot-value condition 'format-arguments) 'not-initialized)
      (setf (slot-value condition 'format-arguments)
	    (loop for slot in (class-slots class)
		  for name = (slot-definition-name slot)
		  unless (or (eq name 'format-control)
			     (eq name 'format-arguments))
		  collect (slot-value-using-class class condition slot))))))

(define-condition STREAM-ERROR (auto-arguments-error)
  ((stream :reader stream-error-stream :initarg :stream)))

(define-condition END-OF-FILE (stream-error)
  ((format-control :initform "End of file encountered on ~s.")))
(define-condition CLOSED-STREAM (stream-error)
  ((format-control :initform "~s is closed.")))

This example happens to be dealing with conditions as opposed to other
kinds of classes, and I think it also happens to meet Kent's
requirements about doing it's setf'ing during initialization.  

However, I think there are (at least) three general issues raised by
this which are not peculiar to conditions, but really about slots in
general:

1. (This is what the original message was specifically about.) How can
user code set the value of slots (in, for example, an
:INITIALIZE-INSTANCE method, if it doesn't know the names of the slots? 
(As noted in my original message, one possibility is to say that you
can't, but must instead define your own accessor method, which had darn
well better be available as an open generic-function and had better be
used in all the right places by the implementation.)

2. (This was alluded to in the original message.) What can/should the
idiom be for writing code which provides default initialization for
slots, where the code to be executed depends on the values of other
slots.  This is probably important enough that it deserves a mechanism
in the language.

3. (This hasn't been brought up at all yet, but is certainly begged by
the code above.)  How should one distinguish between different sets of
slots (i.e., my slots, your slots, etc.) and how should they be
ordered?  In the :INITIALIZE-INSTANCE method, above, I use all the
slots, in the order they happen to be.  I filter out format-arguments
and format-control, but there are undoubtedly others that should be
filtered.  One means is by package: I should only collect slots who's
names are (accessible in) certain packages.
Another example of this kind of thing is in internet object models (e.g.
LDAP) where one tends to distinguish between "user attributes" and
"implementation (or subschema) attributes".  (I haven't addressed
ordering at all.)
From: Pekka P. Pirinen
Subject: Re: New initforms for inherited slots vs simple-condition-format-control
Date: 
Message-ID: <ix676w8jyv.fsf@gaspode.cam.harlequin.co.uk>
"Howard R. Stearns" <······@elwood.com> writes:
> Here's the extended version of the code which triggered all this. 
> AUTO-ARGUMENTS-ERROR is a kind of simple-error in which the
> format-arguments can be :initarg'ed in the usual way, but otherwise is
> built automatically by listing all the other "relevent" values.

I think you're breaking modularity, since AUTO-ARGUMENTS-ERROR doesn't
know which slots are "relevant".  As you note (your point 3), the
language doesn't provide any way of distinguishing sets of slots.
ISTM you should sort it out, either by some additional communication
between AUTO-ARGUMENTS-ERROR and its subclasses, or a new metaclass.

> However, I think there are (at least) three general issues raised by
> this which are not peculiar to conditions, but really about slots in
> general:
> 
> 1. (This is what the original message was specifically about.) How can
> user code set the value of slots (in, for example, an
> :INITIALIZE-INSTANCE method, if it doesn't know the names of the slots? 

I'm not sure this is a desirable thing to have.  Often slot names
would be hidden in order to prevent subclasses from touching them.

Note that in your original problem, we have a public initarg, so there
is a way to solve it:

(define-condition SIMPLE-FILE-NOT-FOUND-ERROR1 (file-not-found-error
					        simple-condition)
  ()
  (:default-initargs :format-control "~a was not found."))
-- 
Pekka P. Pirinen
Harlequin Group plc
 If you don't succeed at first, try again. Then quit. No use of being a
 damn fool about it.  - W. C. Fields
From: Howard R. Stearns
Subject: Re: New initforms for inherited slots vs simple-condition-format-control
Date: 
Message-ID: <3717B652.6EA46E30@elwood.com>
Pekka P. Pirinen wrote:
> 
> "Howard R. Stearns" <······@elwood.com> writes:
> > Here's the extended version of the code which triggered all this.
> > AUTO-ARGUMENTS-ERROR is a kind of simple-error in which the
> > format-arguments can be :initarg'ed in the usual way, but otherwise is
> > built automatically by listing all the other "relevent" values.
> 
> I think you're breaking modularity, since AUTO-ARGUMENTS-ERROR doesn't
> know which slots are "relevant".  As you note (your point 3), the
> language doesn't provide any way of distinguishing sets of slots.
> ISTM you should sort it out, either by some additional communication
> between AUTO-ARGUMENTS-ERROR and its subclasses, or a new metaclass.
> 
> > However, I think there are (at least) three general issues raised by
> > this which are not peculiar to conditions, but really about slots in
> > general:
> >
> > 1. (This is what the original message was specifically about.) How can
> > user code set the value of slots (in, for example, an
> > :INITIALIZE-INSTANCE method, if it doesn't know the names of the slots?
> 
> I'm not sure this is a desirable thing to have.  Often slot names
> would be hidden in order to prevent subclasses from touching them.
> 
> Note that in your original problem, we have a public initarg, so there
> is a way to solve it:
> 
> (define-condition SIMPLE-FILE-NOT-FOUND-ERROR1 (file-not-found-error
>                                                 simple-condition)
>   ()
>   (:default-initargs :format-control "~a was not found."))

BRILLIANT!  I never ever use :default-initargs, so it never occurs to me
to use them, so I never ever use them....   I'll have to think about it
over the weekend, but I want to thank you right away because as I run
out the door, it seems like your suggestion is exactly what I was
looking for someone to say in response to my original posting.

Along the way, I came up with all sorts of other fixes that I didn't
like, which is why I wanted feedback.  Yes, I agree with your comments
about breaking modularity and keeping subclasses from touching slots.  I
was looking at having access to slot names as a poor means of solving
all these problems, and I agree that they open up other problems.  I'm
glad that there is a solution to my original problem that does not
involve setting slots.  

I think the idea is that when someone writes code that is to be
extended, there are two things that they can do to help later users:
either use generic functions as accessors, which can then be subclassed,
or use documented :initargs which can be defaulted.  In the case of
conditions for this example, ANSI didn't require the former, but
fortunately did require the latter.

This still leaves open the issues about generally initializing slots
with computations that depend on the values of other slots, and how to
control what other slots one has access to, but there is no reason to
let these other issues drag down the value of your suggestion for the
immediate problem.

> --
> Pekka P. Pirinen
> Harlequin Group plc
>  If you don't succeed at first, try again. Then quit. No use of being a
>  damn fool about it.  - W. C. Fields
From: Kent M Pitman
Subject: Re: New initforms for inherited slots vs simple-condition-format-control
Date: 
Message-ID: <sfwhfqgrxpm.fsf@world.std.com>
"Howard R. Stearns" <······@elwood.com> writes:

> > (define-condition SIMPLE-FILE-NOT-FOUND-ERROR1 (file-not-found-error
> >                                                 simple-condition)
> >   ()
> >   (:default-initargs :format-control "~a was not found."))
> 
> BRILLIANT!  I never ever use :default-initargs, so it never occurs to me
> to use them, so I never ever use them....

Still leaves you having to pass :format-arguments (as a list), 
even though there's only one argument.  But yes, this simplifies a
lot.

On the other hand, I can't figure out why it matters to you to be mixed
with SIMPLE-CONDITION here.  SIMPLE-CONDITION is not a condition anyone
is required to signal, and it's stylistically pretty marginal to handle
it.  Well, it's stylistically ESPECIALLY ugly to handle SIMPLE-CONDITION
since it spans the serious/non-serious gap; it'd make a little more
sense to me for you to handle SIMPLE-ERROR.

Incidentally, nothing keeps you from doing:

(define-condition easy-file-not-found (file-not-found-error)
  ((format-control :initarg :format-control :reader format-control
                   :initform nil)
   (file :initarg :file :reader file))
  (:report (lambda (condition stream)
             (format stream (or (format-control condition) "~A was not found.")
                     (file condition)))))

So that someone can either
 (error 'easy-file-not-found :file loser)
or
 (error 'easy-file-not-found :file loser 
      :format-control "~A no fue' encontrado.")

In other words, although I can see that you're really desperately searching
for a way to make SIMPLE-ERROR work here, I can't figure out why in the
sense that the problem you have is general to any case where someone wrote
a protocol they didn't intend you to extend and you're mad that they didn't
give you all the tools you need to extend it.  Since there is nothing 
about SIMPLE-ERROR that requires its use, the question is why you care
that you can't use it.  Why don't you just create the equivalent?

> I think the idea is that when someone writes code that is to be
> extended, there are two things that they can do to help later users:
> either use generic functions as accessors, which can then be subclassed,
> or use documented :initargs which can be defaulted.  In the case of
> conditions for this example, ANSI didn't require the former, but
> fortunately did require the latter.

There was never an intent that simple-condition could be or should be
extended.  They were intended for use with people too lazy to make
type-specific errors, but it was assumed that the people who did make
something easier to use would do so in a better way.

Even simple-type-error is a total disaster to implement if you're the system
implementor--it's very painful to call and I have never seen anyone do
so.  I'd take it out in a heartbeat if I could.

> This still leaves open the issues about generally initializing slots
> with computations that depend on the values of other slots, and how to
> control what other slots one has access to, but there is no reason to
> let these other issues drag down the value of your suggestion for the
> immediate problem.

I'm still willing to believe this is a problem although this particular
example (because it involves simple-error isn't every compelling to me).
It's not that I disbelieve you that there are other examples, I just wish
I had one so that I could apply some domain-specific intuition by seeing
the problem in context.  Reasoning about this issue in the abstract is hard.
From: Howard R. Stearns
Subject: Re: New initforms for inherited slots vs simple-condition-format-control
Date: 
Message-ID: <371B4C98.A6526E2F@elwood.com>
Kent M Pitman wrote:
> 
> "Howard R. Stearns" <······@elwood.com> writes:
> 
> > > (define-condition SIMPLE-FILE-NOT-FOUND-ERROR1 (file-not-found-error
> > >                                                 simple-condition)
> > >   ()
> > >   (:default-initargs :format-control "~a was not found."))
> >
> > BRILLIANT!  I never ever use :default-initargs, so it never occurs to me
> > to use them, so I never ever use them....
> 
> Still leaves you having to pass :format-arguments (as a list),
> even though there's only one argument.  But yes, this simplifies a
> lot.
> 
> On the other hand, I can't figure out why it matters to you to be mixed
> with SIMPLE-CONDITION here.  SIMPLE-CONDITION is not a condition anyone
> is required to signal, and it's stylistically pretty marginal to handle
> it.  Well, it's stylistically ESPECIALLY ugly to handle SIMPLE-CONDITION
> since it spans the serious/non-serious gap; it'd make a little more
> sense to me for you to handle SIMPLE-ERROR.
> 
> Incidentally, nothing keeps you from doing:
> 
> (define-condition easy-file-not-found (file-not-found-error)
>   ((format-control :initarg :format-control :reader format-control
>                    :initform nil)
>    (file :initarg :file :reader file))
>   (:report (lambda (condition stream)
>              (format stream (or (format-control condition) "~A was not found.")
>                      (file condition)))))
> 
> So that someone can either
>  (error 'easy-file-not-found :file loser)
> or
>  (error 'easy-file-not-found :file loser
>       :format-control "~A no fue' encontrado.")
> 
> In other words, although I can see that you're really desperately searching
> for a way to make SIMPLE-ERROR work here, I can't figure out why in the
> sense that the problem you have is general to any case where someone wrote
> a protocol they didn't intend you to extend and you're mad that they didn't
> give you all the tools you need to extend it.  Since there is nothing
> about SIMPLE-ERROR that requires its use, the question is why you care
> that you can't use it.  Why don't you just create the equivalent?

I can and I did.  But, gee, if I see myself writing even the tiniest
code fragment that I KNOW must have already been written by me or the
implementor of some other code that I am using, I want to use it.  If I
can't, then it makes me wonder if there isn't some problem with the
language.  (In this case, the area of concern is really CLOS, not the
condition system.)

> 
> > I think the idea is that when someone writes code that is to be
> > extended, there are two things that they can do to help later users:
> > either use generic functions as accessors, which can then be subclassed,
> > or use documented :initargs which can be defaulted.  In the case of
> > conditions for this example, ANSI didn't require the former, but
> > fortunately did require the latter.

So, just to review, it turns out that there are two ways one might be
able to provide a value that gets used by the system:

  1. Make your own slot with a generic-function accessor that you know
will be used by the system in reporting errors.  This one turns out not
to be possible here because it turns out that
simple-condition-format-control isn't required to be generic.  There are
reasons for this, and that's fair. Furthermore, the fact that it doesn't
happen to be a solution here is not necessarily an indication that there
is a problem in CLOS.  On the other hand, it this were the only
solution, then we might want to acknowledge it's importance and this
would have influence on related issues, such as whether or not
simple-condition-format-control should be required to be generic. 

  2. Fortunately, there is another solution: :default-initargs.

I think that the conclusion I draw is that someone writing a class that
is to be extended should think long and hard about making one or both of
these solutions available.

> 
> There was never an intent that simple-condition could be or should be
> extended.  They were intended for use with people too lazy to make
> type-specific errors, but it was assumed that the people who did make
> something easier to use would do so in a better way.

I guess I don't understand why not.  I'm thinking that I want to define
some specialized condition -- a kind of file-error, control-error or
whatever -- and not require the user to necessarily have to specify a
format control string or report function.  (It's not that I want to
extend simple-condition, but that I want to extend some other class in a
way that I happen to know is already provided by simple-condition.)  It
seems (naively?) obvious to me that I then want to create a condition
that mixes in both the "main" condition type (file-error, control-error,
etc.) and simple-condition.  Yes, I could leave out simple-condition and
define my own report function which duplicates the work of the report
function in simple-condition, but WHY?

Am I wrong to think this way?

> 
> Even simple-type-error is a total disaster to implement if you're the system
> implementor--it's very painful to call and I have never seen anyone do
> so.  I'd take it out in a heartbeat if I could.

I use it.  This is probably off point, but here's the code.  (I know I'm
going to regret this...)

(defun interactive-evaluated-form ()
  (list (read-evaluated-form)))

(defun check-type-error (place value type type-string)
  (let ((cond (make-condition
	       'simple-type-error
	       :datum value :expected-type type
	       :format-control "The value of ~s, ~s, is not ~a."
	       :format-arguments
	       (list place value
		     (or type-string
			 (format nil "of type ~s" type))))))
    (restart-case (error cond)
      (store-value (value)
	:report (lambda (stream)
		  (format stream "Supply a new value for ~S."
			  place))
	:interactive interactive-evaluated-form
	value))))


;;; IWBNI this evaluated subforms of place only once, but the spec
;;; doesn't require this, and no one else does the fancy way, so we
;;; won't either.  Similarly for assert, ctypecase and ccase.
(defmacro CHECK-TYPE (place type &optional type-string)
  (with-unique-names (var block)
    `(block ,block
       (loop
	(let ((,var ,place))
	  (when (typep ,var ',type) (return-from ,block nil))
	  (setf ,place
		(check-type-error ',place ,var ',type ,type-string)))))))

(defmacro CTYPECASE (keyform &rest clauses)
  (with-unique-names (block)
    `(block ,block
       (tagbody
	,block
	(return-from ,block
	  (typecase ,keyform
	    ,@clauses
	    (otherwise
	     (setf ,keyform
		   (check-type-error
		    ',keyform ,keyform
		    '(or ,@(mapcar #'first clauses)) nil))
	     (go ,block))))))))

(defmacro CCASE (&environment env keyform &rest clauses)
  (let ((block (gensym "CCASE"))
	(types (case-keys clauses env)))
    `(block ,block
       (tagbody
	,block
	(return-from ,block
	  (case ,keyform
	    ,@clauses
	    (otherwise
	     (setf ,keyform
		   (check-type-error
		    ',keyform ,keyform '(member ,@types) nil))
	     (go ,block))))))))



> 
> > This still leaves open the issues about generally initializing slots
> > with computations that depend on the values of other slots, and how to
> > control what other slots one has access to, but there is no reason to
> > let these other issues drag down the value of your suggestion for the
> > immediate problem.
> 
> I'm still willing to believe this is a problem although this particular
> example (because it involves simple-error isn't every compelling to me).
> It's not that I disbelieve you that there are other examples, I just wish
> I had one so that I could apply some domain-specific intuition by seeing
> the problem in context.  Reasoning about this issue in the abstract is hard.

Providing default initializations for slots with computations that
depend on the values of other slots?  

+ The Icad system is used by mechanical engineers for automatically
generating complex, parameterized models.  One of it's fundamental
concepts is that the value of a any "attribute" of a part can be a
computation based on the value of any other attribute.  Indeed, that
computation can also be the default value if the "parent" of the part
doesn't provide some other value.  This is more general than what I
asked for, but I do think its fair to say that each complex part might
involve tens or hundreds of :defaulted-inputs which have computations
that depend on the value of other 
attributes.

+ I'm not a CLIM expert, but it seems that layout would be easier if one
could specify a sheet type with a default width that depended on some
compuation involving other parameters such as height, number and width's
of child sheets, etc.

+ In implementing buffered streams, one wants to be able to initialize
slots such as "ultimate-simple-vector",
"offset-to-ultimate-simple-vector",
"ultimate-simple-vector-terminal-position", etc., based on a given
user-provided buffer.  Implementing buffered file streams, one wants to
reuse the same class, but allow that "user-provided buffer" to actually
be defaulted to some newly allocated or resourced array based on the
stream-element-type.  Suppose one further wants "buffered-input-stream",
and "buffered-file-input-stream" to be classes useable and extendable by
the users, and you don't want to have to document that there are all
these internal things that have to be initialized in a precise way when
they are used.  

In all these cases, it is awkward to require the user of the class to
use a special function for instantiation.  Instead, one wants
make-instance and friends to do the right thing.  This is because the
specialized defaulting behavior is extendable behavior associated with
the class itself, and not with some application which uses the class. 
Indeed, one wants be able to define new classes which might change SOME
of these default initializations without requiring to much attention to
mix-in order or providing newly specialized non-generic helper functions
for part instantiation.
From: Kent M Pitman
Subject: Re: New initforms for inherited slots vs simple-condition-format-control
Date: 
Message-ID: <sfw7lr8bdit.fsf@world.std.com>
"Howard R. Stearns" <······@elwood.com> writes:

> > In other words, although I can see that you're really desperately searching
> > for a way to make SIMPLE-ERROR work here, I can't figure out why in the
> > sense that the problem you have is general to any case where someone wrote
> > a protocol they didn't intend you to extend and you're mad that they didn't
> > give you all the tools you need to extend it.  Since there is nothing
> > about SIMPLE-ERROR that requires its use, the question is why you care
> > that you can't use it.  Why don't you just create the equivalent?
> 
> I can and I did.  But, gee, if I see myself writing even the tiniest
> code fragment that I KNOW must have already been written by me or the
> implementor of some other code that I am using, I want to use it.  If I
> can't, then it makes me wonder if there isn't some problem with the
> language.  (In this case, the area of concern is really CLOS, not the
> condition system.)

This is fine.  Just understand that this particular sliver of the language
was specifically designed not to be easily extensible exactly so that it
didn't have to require CLOS.  As such, it is not an example of what CLOS
or the modern condition system seeks to be--it is specially designed
to atrophy from disuse, and you are apparently trying to keep it alive.

The right way to do what you want to do is to wait until the language
can again be modified, if ever, or to build a layered dialect that 
redefines this.  But trying to warp a definition that was specifically
made not to give the flexibility you offer is (in my opinion, for whatever
that's worth) not very productive.

> So, just to review, it turns out that there are two ways one might be
> able to provide a value that gets used by the system:
> 
>   1. Make your own slot with a generic-function accessor that you know
> will be used by the system in reporting errors.  This one turns out not
> to be possible here because it turns out that
> simple-condition-format-control isn't required to be generic.

Nor is it possible, in conforming code, to make accessors.

I'm not trying to make this hard.  It wasn't my idea to make this not be
CLOS-based.  But certain strong factions when the language standard was
cast in concrete did worry about excess dependence on an untested CLOS
that they weren't sure people would like, weren't sure they wanted to
implement, etc.
 
>   2. Fortunately, there is another solution: :default-initargs.
> 
> I think that the conclusion I draw is that someone writing a class that
> is to be extended should think long and hard about making one or both of
> these solutions available.

I really think the condition system as presented (because of the 
read-only-class thing) intends you to "extend" classes only by adding
additional init keywords and by using :default-initargs (or, if you
know the slot-name--which you don't for system conditions, by doing
:initform in a slot spec for the same slot in the inheriting class).

> > There was never an intent that simple-condition could be or should be
> > extended.  They were intended for use with people too lazy to make
> > type-specific errors, but it was assumed that the people who did make
> > something easier to use would do so in a better way.
> 
> I guess I don't understand why not.

I guess I'm saying it's an artifact of history and that the right way to
understand it is as a consequence of voting rules.

To understand why people voted the way they did, you have to look to their
budget, their areas of expertise, the number of other things they had going
at the same time, the pressure from customers for small images, the sense
that small vendors had that large vendors were cluttering the image with
lots of functionality, etc.  They wanted a separable CLOS and Condition
System--they wanted to be able to signal errors without requiring CLOS.

Do not look to coherent technical design to find this answer.  It is not
there.  You have to see this as the committee  product that results when
one community seeks no CLOS and one seeks a unified condition system with
CLOS.  You end up with something neither community exactly wants, but that
makes each suffer pain in service of the other.  At least the result WAS
useful--there were other compromises I saw happen in which it isn't
clear that the compromise WAS useful to anyone.

> I'm thinking that I want to define
> some specialized condition -- a kind of file-error, control-error or
> whatever -- and not require the user to necessarily have to specify a
> format control string or report function.

Absolutely.  No one is saying you're asking for something conceptually
unreasonable.  If you got that impression from me, that's your problem.
I absolutely agree you want to be able to do this.  Somehow.
But others didn't want this CLOS-based at all.  Given that you have CLOS,
what you say makes sense.  Given the universe in which people didn't
want CLOS, what you asked for is bad.  Political situations just come
out that way.  Just look at the "abortion debate" in US politics--the
Roe v. Wade trimester system makes neither camp very happy--one group
wants abortions whenever needed and one wants them not at all--instead you
get a staged system that is really antithetical to both positions, that
causes each group to feel some pain, and that causes each group to feel
it at least got some sense of satisfaction in that their opponents
didn't get everything they wanted.  Same deal...

> Am I wrong to think this way?

No, but you are wrong to think that everyone who voted on this spec
did.  It's been too long since I had to remind people not to be so quick
to seek technical answers to the hard questions in how language design
came about.  A great deal of it is personality, political, chance, etc.


> > I'm still willing to believe this is a problem although this particular
> > example (because it involves simple-error isn't every compelling to me).
> > It's not that I disbelieve you that there are other examples, I just wish
> > I had one so that I could apply some domain-specific intuition by seeing
> > the problem in context.  Reasoning about this issue in the abstract is hard.
> 
> Providing default initializations for slots with computations that
> depend on the values of other slots?  

I understand this in general, and it makes sense for regular classes.
I wanted to see specific examples for conditions because I wanted to
see if there were cases where you need to be able to do anything after
the instance initialization.  I *think* (personally) the right answer
is to spec out read-only metaclasses saying that they are permitted to
set slot-value during instance initialization only but not to set
slots after the signaling begins.  (This would mean that if you
pre-cache an instance, you may not change its slot values when you go
to resignal it.)  Since the examples you provided are not about
conditions, I can't get any useful data, but my sense is still that
nothing you've asked for is inconsistent with what I've said.

All the other examples you provided can be done easily already using
existing technology.

> In all these cases, it is awkward to require the user of the class to
> use a special function for instantiation.

Nor is one forced to do it because in all of these cases you're not
talking about condition classes, so nothing keeps you from just having
(initialize-instance :after) methods that do the setup you want.
Are you saying you don't think you can do that for regular standard-class?
From: Howard R. Stearns
Subject: Re: New initforms for inherited slots vs simple-condition-format-control
Date: 
Message-ID: <371CBE94.45A785B7@elwood.com>
OK, I think I understand where we're talking past each other, Kent.

1. I am thinking of the condition system (assuming that it is
implemented to be compatible with CLOS, which I understand that it
doesn't have to be), as being perfectly fine.  Actually, I'm interested
to hear what you think is wrong with it.  Personally, I have only a tiny
group of quibbles which we can address in another thread if you like. 
In general, though, it never even occured to me that it should be
scrapped in favor of something else.

2. The issue of providing new initforms for inherited slots happened to
come up for me with a condition example, but I never really thought of
it as a condition issue.  Instead, again, I was thinking of conditions
as being implemented "compatibly" with CLOS and was really thinking
about CLOS in general.  That's why all my other example had nothing to
do with conditions.  (In fact, the only reason I posted here instead of
comp.lang.clos as I originally intended was that this first example
happend to use conditions and the "solution" that occured to me involved
supplying reader methods, but it turned out that CL disn't require them
to be generic -- therefore, not a clos issue.) And yes, I think you are
right that IFF one can use :default-initargs or whatever to supply
appropriate initial values, then there is probably no reason to make
conditions be modifiable.  

3. Despite my use of phrases like "a problem with the language", I'm
really looking for general problems which could be addressed in future
revisions as opposed to trying to find "errors" in previous work.  I'm a
pragmatist, and this is one reason I like the EXISTING ANSI CL spec so
much.  I'm just trying to improve it.  I think I do understand how it
came about, and I really don't have any problem with that.  (I think you
know that, but I just want to be clear.)

4. Along the way, I brought up this issue of providing default values of
CLOS slots which involve computations based on the value of other slots,
and I gave CLOS examples of where I might want to do this.  I'm looking
for a standard idiom for doing this, idealy an existing one as well
suited as :default-initargs to my original problem.  You mention that
this is easy, and suggest (initialize-instance :after).  

This is, of course, what I have been doing (as shown in example code in
this thread).  However, there are problems with this approach:

  + :after initialize-instance is too late to be supplying things as
:default-initargs, so one is back to depending on either a documented
slot-name or writer method.

  + Determining whether the value has already been supplied is awkward. 
One must check for &key args (possibly with different names) and/or
examine whether the slot is already boundp (which brings back the
problem of knowing the slot-name), and also the list of slot-names in
shared-initialize (again, knowing slot-names, and indicating that
shared-initialize might sometimes be required instead of
initialize-instance).

  + Ordering a series of make-xxx-/initialize-xxx :after methods for a
series of classes which can be mixed in different orders is a
mightmare.  I find myself (in the buffered streams example) using
different methods (shared-initialize vs initialize-instance) just so I
know better what order things will come in.  More generally, imagine a
series of classes which you want to use and extend, and all you care
about is that they have the right default values at the end of
instantiation.  You don't really want to know which does this by using
which :after methods.  

I SUSPECT that the right thing is a separate construct, like
:default-initargs, which can be implemented in different ways, but which
allows the user to declare computations for slots which are not
otherwise initialized.  The implementation might perform a topological
sort of these to get the order right.  I'm hoping that isn't really
necessarry, but after appreciating how much work ICAD system stuff was
doing...
From: Kent M Pitman
Subject: Re: New initforms for inherited slots vs simple-condition-format-control
Date: 
Message-ID: <sfw90bmkjaz.fsf@world.std.com>
"Howard R. Stearns" <······@elwood.com> writes:

>   + :after initialize-instance is too late to be supplying things as
> :default-initargs, so one is back to depending on either a documented
> slot-name or writer method.

I think this as well as some of your other objections has to do with
who's supplying what arg.  I don't think you can just willy-nilly
drop values here and there and expect the system to just "make things work".
If you are putting something in :default-initargs, that means a specific
thing--it means "if i don't supply an initarg, please use this one".
That happens pretty early.  If you don't want it to happen that early,
it's not that this mechanism is broken but that you don't want to be using
this mechanism.  Whether this is too late is not an absolute thing but
a per-domain thing and you have not identified a domain here, so we can't
tell if it's too late or not.

>   + Determining whether the value has already been supplied is awkward. 
> One must check for &key args (possibly with different names) and/or
> examine whether the slot is already boundp (which brings back the
> problem of knowing the slot-name), and also the list of slot-names in
> shared-initialize (again, knowing slot-names, and indicating that
> shared-initialize might sometimes be required instead of
> initialize-instance).

Again here I think whether you use NILness or boundpness or something else
as meaning unsupplied is a per-slot issue, part of the domain application.
If you are trying to write debugging utilities, you should be operating
in the meta-object protocol and not hassling with any of this stuff.
If you are writing user code, you simply can't write user code in CLOS that
maps across all slots as if they are either initialized or not or as if
failing to initialize them is wrong -- for all you know, a user program
depends on uninitialization to carry meaning.  But for any given slot
where you know the protocol, either the protocol is well-defined and 
properly  documented.  If it is not well-defined, your gripe is with the
programmer, not the language.  If it is not well-documented, again your
problem is not with the language.  If it is well-defined and well-documented
and you are still not happy, your problem is with the fact that there is
more than one person in the world and this simple fact is enough to assure
that the world will not always be in agreement, clos or not.  Something
in what you keep saying leads me to believe you're looking for an overarching
theory of how all initializations should be done for all slots in all instances
of all classes in all applications everywhere.  If there were such a theory,
I suspect we could get away with only half of the mechanism  we have now;
that is, I suspect we have too much mechanism for the tightly controlled
world you hint at, not too little.  I don't mean to characterize you as
a vicious fascist bent on world domination here, though I guess it comes
out sounding that way--I just use this kind of strong language to help you
better see the issues  that are catching my eye.

>   + Ordering a series of make-xxx-/initialize-xxx :after methods for a
> series of classes which can be mixed in different orders is a
> mightmare.

Not if done inductively on a per-method basis.  If you wrote the individual
classes correctly to be composed, the natural order of method selection
will do the right thing.  If you later went back and patched a whole
configuration in order to achieve a machiavellian (sp?) effect without
sensitivity to the underlying modularities, you can easily code yourself
into a corner.  The system does give you enoguh rope to hang yourself.

> I find myself (in the buffered streams example) using
> different methods (shared-initialize vs initialize-instance) just so I
> know better what order things will come in.

It seems to me that having to put things in different places for each slot
or method is a necessary complication of the fact that some things have to
precede others and that not each thing is used in uniform ways by each class.
(That's why we use different classes and different slots--because one piece
of data and one way of using it doesn't suffice.)  And so I don't see it as
a weakness that sometimes things go in different places any mroe than I see
it as a weakness that some things go in the consequent of an IF and some
things go in the alternative... it's just part of the way things are.
But I might be missing something--feel free to get more concrete if you want
and I'll try again.

> More generally, imagine a
> series of classes which you want to use and extend, and all you care
> about is that they have the right default values at the end of
> instantiation.  You don't really want to know which does this by using
> which :after methods.  

I can't do this in my model of the world.  I can only imagine implementing
each class in a way that makes sense for all of its clients.  Trying to
engineer a class so that it works correctly in context of some specific
client doesn't make sense to me.
 
> I SUSPECT that the right thing is a separate construct, like
> :default-initargs, which can be implemented in different ways, but which
> allows the user to declare computations for slots which are not
> otherwise initialized.  The implementation might perform a topological
> sort of these to get the order right.  I'm hoping that isn't really
> necessarry, but after appreciating how much work ICAD system stuff was
> doing...

This sounds weird to me because it seems to presuppose that all slots
will have the same desire to become initialized at initialize time.
You can make such an overarching theory--but it's a lot for CLOS to
impose this theory on all applications.  And having introduced such a
theory, I see no way that it wouldn't create pressure on people to
initialize things they might not have wanted to initialize.
From: Howard R. Stearns
Subject: default values involving computations of other slots
Date: 
Message-ID: <371DF4C7.3106B460@elwood.com>
Now we're on a different subject.

This isn't an urgent problem, and indeed, there is a commercial system
(the ICAD system) that does this and more.  I'm not working on this now
at all. I'm just daydreaming here about whether and how somone could
provide an extension to CLOS (possibly using only CLOS, possibly
requiring the MOP, and possibly) requiring a new implementation of
CLOS).  

The discussion here is now just for general amusement.  I note that
there's another related thread going, and yes, I suppose I am looking
for a grand-unified-initialization theory.  If no one is naive enough to
think that one might exist, how will we ever find one?

The extension would allow a subset of the behavior provided by ICAD --
specifically, it would allow default slot initializations based on the
values of other slots.  On the other hand, it would do this in a way
that allowed all the normal CLOS/MOP behavior in every other respect.
By contrast, ICAD parts have their own object system and don't
participate in all the normal CLOS protocols.  For instance, attributes
(slots) are accessed by message passing and, for our purposes here, are
essentially read-only.
 
Here's an example of the kind of thing one can do with
ICAD. (Anti-disclaimer: I haven't consulted for them in years, and no 
longer own any stock, so I think I'm allowed to say that I think it's
pretty darn cool stuff.)

(defpart window (base-object)
  :defaulted-inputs
  (:width (* 2 (the :height))
   :height (/ (the :width) 2)
   :border-size (* *border-factor* (the :weight))
   :density 1.0)
  :attributes
  (:weight (* (the :density) (the :height) (the :width))))

One could provide various combinations of inputs such as:
(make-part 'window :width 6)
(make-part 'window :height 3)
(make-part 'window :height 3 :width 5)
(make-part 'window :height 3 :density 3)
(make-part 'window :height 3 :border-size 2)
(make-part 'window) ==> #<some part 123>
(the-object #<some part 123> :weight)
 => Error: circular reference between width and height.

Notice that I didn't need to specify what order things get computed in.
In fact, the values of attributes/defaulted-inputs are not computed at
all untill/unless they are demanded.  Only when asked does the attribute
and its dependencies get computed.  It is not obvious whether such
"demand-driven evaluation" is actually required to provide "computed
default-initargs", or whether it's orthogonal.

One can extend window: (I'm approximating the syntax.)

(defpart my-window (window)
  :defaulted-inputs
  (:border-size (* *border-factor* (sqrt (the :weight) (the :height)))
   :width (sum :children :width)
   :child-width 1)
  :parts
  ((children :quantify (:lateral 3)
             :type 'box
             :width (the :child-width))))

(defpart 3d-window (my-window)
  :defaulted-inputs
  (:length (frob (the :height) (the :width)))
  :attributes
  (:weight (* (the :density) (the :height) (the :width) (the :length))))

Note that the inheritance effects only which "rules" are used to compute
the value -- it does not directly effect the order of computation.
From: Raymond Laning
Subject: Re: New initforms for inherited slots vs simple-condition-format-control
Date: 
Message-ID: <3724640A.3959683@ix.netcom.com>
"Howard R. Stearns" wrote:

> OK, I think I understand where we're talking past each other, Kent.

<snip>

>
> 4. Along the way, I brought up this issue of providing default values of
> CLOS slots which involve computations based on the value of other slots,
> and I gave CLOS examples of where I might want to do this.  I'm looking
> for a standard idiom for doing this, idealy an existing one as well
> suited as :default-initargs to my original problem.  You mention that
> this is easy, and suggest (initialize-instance :after).
>
> This is, of course, what I have been doing (as shown in example code in
> this thread).  However, there are problems with this approach:
>
>   + :after initialize-instance is too late to be supplying things as
> :default-initargs, so one is back to depending on either a documented
> slot-name or writer method.
>
>   + Determining whether the value has already been supplied is awkward.
> One must check for &key args (possibly with different names) and/or
> examine whether the slot is already boundp (which brings back the
> problem of knowing the slot-name), and also the list of slot-names in
> shared-initialize (again, knowing slot-names, and indicating that
> shared-initialize might sometimes be required instead of
> initialize-instance).
>
>   + Ordering a series of make-xxx-/initialize-xxx :after methods for a
> series of classes which can be mixed in different orders is a
> mightmare.  I find myself (in the buffered streams example) using
> different methods (shared-initialize vs initialize-instance) just so I
> know better what order things will come in.  More generally, imagine a
> series of classes which you want to use and extend, and all you care
> about is that they have the right default values at the end of
> instantiation.  You don't really want to know which does this by using
> which :after methods.
>
> I SUSPECT that the right thing is a separate construct, like
> :default-initargs, which can be implemented in different ways, but which
> allows the user to declare computations for slots which are not
> otherwise initialized.  The implementation might perform a topological
> sort of these to get the order right.  I'm hoping that isn't really
> necessarry, but after appreciating how much work ICAD system stuff was
> doing...

If you are willing to pre-empt the user's ability to write accessors for
slots, which is not all that unreasonable given that you want to provide
behavior for them based on other slots' values anyway, you could provide
them with some macrology to do that.  Such was the approach taken with the
Concept Modeller  - the distinction between slot and accessor was lost in
the process.  But lazy evaluation allowed us to cheat - we could rely on
CLOS's topological sort to select the right behavior for the slot, and not
be concerned about evaluation order since that occurred much later, after
all the excitement of initializing the object had died down.  If you can
afford to wait until later, the macros to do such things are small indeed.
It occurs to me that this could be done without pre-empting the accessors as
well, although I'm not sure how you would handle a user's definition when it
conflicted with your formula.