comp.lang.ada
 help / color / mirror / Atom feed
From: jgv@swl.msd.ray.com (John Volan)
Subject: Re: Generic association example (was Re: Mutual Recursion Challenge)
Date: Mon, 31 Oct 1994 17:34:04 GMT
Date: 1994-10-31T17:34:04+00:00	[thread overview]
Message-ID: <1994Oct31.173404.22096@swlvx2.msd.ray.com> (raw)
In-Reply-To: EACHUS.94Oct27105337@spectre.mitre.org

eachus@spectre.mitre.org (Robert I. Eachus) writes:

> In article <1994Oct26.232154.29094@swlvx2.msd.ray.com> jgv@swl.msd.ray.com (John Volan) writes:
> 

[snip my objection about the need for the client to do narrowing to get
a fuller view of an associated object after a query]

>   Let the compiler do the work.  If there is only one concrete type
>in an abstract class, the check might result in a call at run-time to
>a check, but the check should be implemented as a return instruction.
>If there are types derived from Office or Person, there may be an
>actual check in some other cases that you want made.  But any compiler
>that does run time checks for abstract types, or builds dispatch
>tables for abstract types has a bug.  (There are cases where you may
>have a view of an object as an abstract type, but that must always be
>a static view--at least, that is the intent.)

It still bothers me that when you query an object like an Employee for
another object associated with it, let's say its Office, the result
you get back will only provide a highly abstract view of that
associated object.  If the client wants to invoke operations on
the second object that are only available from a more complete view
(for instance, we want to find out the Building the Office is in) the
client has to do a narrowing type-cast on it.  I just think it would
have been nice if the original query operation provided the more
complete view of the associated object in the first place.  But to
do that, you somehow have to solve the chicken-and-egg problem.

To be fair, even under my generic scheme, an associative query is only
going to provide a "highly abstract view", and the client still has to
do a conversion to a more complete view.  But that "highly abstract
view" is one of those Identity.Value types, so the effect is a bit
different.  An Identity.Value type is just a "black box" with a
one-to-one relationship with a particular access-to-classwide-type,
and that access type presumably is exactly the "complete" view we're
looking for.  In theory, the translation shouldn't need to involve any
run-time checks -- but, of course, that depends on how you actually
implement the "black box."

>  > What cannot be easily named, cannot be easily explained, and
>  > therefore cannot be easily understood.

>    The style of Ada 9X programming I am focusing on addresses this by
>making that disappear from sight.  The only objects visible in the
>"main" program should be complete objects of complete types.  The rest
>should be treated like making sausage or scrapple and hidden in
>private parts whereever possible.

Okay, okay, I believe you.  I guess it would help me to see a
completely worked-out example (or to sit down and work one out
myself).  Believe me, I'm very enthusiastic about any scheme that can
let you snap together a class from reusable parts.  But I don't see
how the association-supporting code for a class can actually be hidden
away in private parts, regardless of how it gets constructed.
Remember, I'm working under the basic assumption that
association-related operations are going to be public features of a
class, in whatever complete view you have for it.

I can see that the final step in constructing a class with
associations would be to just derive a final concrete type (with null
extension) from the last abstract type coming out of a chain of these
association-instantiations.  But this means that a reader must examine
this whole chain of instantiations to fully understand the class.
Yes, I see how that might be mitigated by using renamings and subtypes
to restate the total class interface accumulated from all the abstract
ancestors....

Hmmm ... those renamings you allude to, do you mean them to be
renamings-as-declarations, or renamings-as-bodies?  I thought you
meant the former, but if you meant the latter, then that opens up a
whole new realm of possibilities.  One upshot of this is that it would
at least be conceivable that we could merge our two techniques: Your
mixin scheme could be used to easily snap together the implementation
backbone for the associations, while my scheme of
deferred-coupling-via-generics could be used in packaging up the final
"total" abstraction for the classes, solving the chicken-and-egg
problem of how to establish mutually-dependent package specs (that is,
if you really *need* the mutual dependency to be publicly visible in
the package specs).

[snip my sketch of mutually-recursive Associate procedures]

>      Excellent reasoning, but I wouldn't code the blasted bodies that
>way.  

Then perhaps a scheme such as yours could provide the actual hidden
implementation.

>Infinite recursion is frowned upon, so if set for office calls
>set for person and vice-versa, something has to break the loop.  

Of course.

>The
>"best" way I found was to set the local attribute, then check the
>remote attribute and if it was "wrong" call the matching set.  It
>works, but it is pretty kludgy.  

Actually, the way I worked it was to always keep things as "local" as
possible: I'd check the local attribute first to see if it was already
"right", and then break the infinite recursion there (on the
assumption that the mutual-pointing invariant condition had always
been satisfied in the past).  Otherwise, I'd set the local attribute
(disassociating the old partner first if necessary), and then make the
mutually-recursive call to the cross-side Associate procedure.  The
same thing would happen on the other side, and then the first
Associate would get called again.  At that point, the recursion would
stop, because the local attribute would turn out to be "right"
already.  Is this better or worse than the way you describe?  I can't
really say, but I'd welcome any comments.

>I'm willing to pay a bit for
>elegance, but that's a little expensive.  Above you talk about
>eliminating redundant checks, here you not only prohibit simple
>operations from being effectively inlined, but require a check that
>the thing you just set has the right value.

Well, I can't deny the expense of the mutual recursion.  And I'm not
sure how I could get rid of the extra check without breaking my own
encapsulation scheme: To do that, each class would have to publish a
non-recursive "Set" operation that merely set the local pointer
without guaranteeing the invariant condition.  This operation could be
called by the "Associate" operation on the other side in order to
fulfill the invariant in an efficient manner.  But this opens up the
possibility of other clients also calling these weaker "Set"
operations, rather than the stronger "Associate" operations, thereby
possibly violating the invariant.  Obviously not a good idea.  (There
might be a tricky way to use generics and access-to-subprogram types
to make these "Set" operations accessible from the cross-side
"Associate" operations without otherwise making them public globally,
but this still won't solve the inlining issue you raise.)

As for mutual recursion preventing a "simple" operation from being
inlined ... hmmm ... that observation is actually triggering some
interesting questions in my mind.  Let me respond to this first by
saying that, yes, you're right, mutual recursion is a liability --
if the operation of setting up an association is really a "simple"
one.

But what if it isn't "simple"? 

This question is actually forcing me to rethink some of my own
intuitions about this whole issue.  You see, in previous posts I
myself agreed to the idea that the attributes and operations
supporting a given association would probably be class-wide, and
orthogonal to those of any other association.  In other words, there
was probably no need to make these into dispatching operations.  And
you probably wouldn't want subclasses to alter the way these
operations behaved by introducing overriding implementations for them.

In fact, I also conceded the possibility that, for some applications,
association operations didn't even need to be primitive or
inheritable.  That's why it was a viable alternative to off-load
these operations to child units, where the "withing" problem
disappears.  Or even to off-load the whole association itself into
an association package.

But now I'm beginning to think that there may be applications where
those assumptions are actually invalid.  In fact, there might be
occasions where you *do* want to make association operations into
primitive, inherited-but-overridable, dispatching operations.  I think
I've been subconsciously groping toward that intuition all this time,
without realizing it.

Oh, don't get me wrong: *Structurally*, I don't think a subclass
should do anything different about setting up the pointers for an
association.  One way or another, association operations in a
subclass should satisfy the mutual-pointing invariant condition, most
likely by just invoking inherited forms of these operations.  (For
that matter, a hidden implementation for these inherited operations
could take advantage of your mixin technique.)

BUT, beyond purely structural considerations, a subclass might
introduce other *behavioral* variations on what happens when an
association is set up (or taken apart).  In fact, these differences
may involve interactions between the association in question and
_other_ _associations_ _the_ _class_ _is_ _involved_ _in_.  In other
words, associations might not always be so orthogonal after all.

As an example, consider these possible application requirements:

   "Whenever any Employee is assigned a new Office, the Manager who
    immediately supervises that Employee shall be notified of the move 
    (via an appropriate Memo generated by the system)."

   "Whenever a Manager is assigned a new Office, all the Employees
    who are immediate subordinates of that Manager shall be notified of
    the move (via an appropriate Memo generated by the system)."

Here we have a situation where an event affecting one association
(Employee gets assigned a new Office) can involve traversing another
association (find and notify the Employee's supervising Manager),
and can involve additional behavior at a subclass level (if an Employee
happens to be a Manager, also find and notify that Manager's subordinate
Employees).

But if this kind of situation is the case, then the whole issue of
inlining becomes a moot point.  You can't do a statically-determined
optimization like inlining if you're in a dynamically-determined
situation like dispatching.

Moreover, consider this possibility:

   "Offices shall be distinguished into Doorless_Offices (i.e., 
    industrial "cattlepens" formed out of modular partitions) versus
    Doored_Offices (actual rooms with solid walls and doors).  Since the
    latter are highly desirable these shall be preferentially assigned
    to Managers.  There shall be a prioritized list of Managers waiting
    for Doored_Offices.  Whenever a Doored_Office becomes available,
    either because of new construction/remodeling, or because a
    previous occupant Employee moves out, the next Manager on the waiting
    list shall be notified (via an appropriate Memo generated by the system)."

Now, when we disassociate an Employee from an Office, we not only need
to dispatch on the type of Employee, but we also need to dispatch on
the type of Office.  The fact that there are *two* classes to dispatch
on makes mutual recursion even more necessary -- such an operation
would be a "multimethod".  It's illegal in Ada9X to have a single
operation dispatch against more than one class.  But it is perfectly
okay to have two operations working in concert, each operation
dispatching against only one of the classes.

>      According to ME ;-) the association package "owns" the Attribute
>fields and is the only one to monkey with them, and this is the right
>and proper approach in Ada 9X.  It is different, but it seems better
>from an encapsulation point of view.

I agree ... as long as the association truly is a separate abstraction
within the problem domain, one that is indeed orthogonal to any other.
But I think that there might be some applications where this is not
the case.  Or rather, only the *structural* aspects of an association
might be truly orthogonal and separable, but the overall *behavior*
connected with an association might not be.

In other words, I'd say your technique is *a* "right and proper Ada9X
approach", but not necessarily *the* "right and proper Ada9X approach"
for all applications.  Be careful: We've been criticizing other
languages for forcing us to accept only "One True Way".  Let's not
simply replace "THEIR One True Way" with "OUR One True Way."  Ada is a
general purpose language with a lot of expressive power.  Its features
ought to be able to support many different styles of design, and even
allow the fruitful merging of different styles.

>With the Smalltalk or C++ model
>large and complex object implementations soon get buried in
>interactions between different components of an object.  In this
>model, each abstraction takes care of its own.

And I think this is a very good thing, a true benefit of your
technique ... as long as these associations are truly separable
abstractions.  But there may be situations where they would not be
separable, and only the classes themselves would be truly separable as
abstractions.  In such cases, the reason we couldn't address the
association operations totally in isolation wouldn't be because of any
artefact of our chosen style of software design, but because of the
irreducible complexity of the problem domain itself.  As Brooks said,
there's "No Silver Bullet" that can eliminate that sort of complexity,
it just has to be dealt with.

We've been staring for so long at this wall of "how to get two types
in two different packages to point to each other", that we've
forgotten that there may be other walls we need to cut through -- and
even some floors and ceilings, too, if we want to install stairwells
and elevators.  My deferred-coupling-via-generics technique really
addresses a broader set of problems than just mutually-pointing types.
It provides a solution for any situation where two or more
abstractions need to be mutually dependent on each other, and the
dependency needs to be a public feature of their respective interfaces
(and perhaps even a primitive feature), yet the different abstractions
still need to be encapsulated in separate packages.

>      I left some stuff out here for simplicity, but in a "real"
>implementation of this idea, I would insist that both object be
>derived from Controlled, or Limited Controlled, 

Total agreement here.

>and I would insure
>that there were no dangling pointers when an object was destroyed, and
>that copying an object did not copy the assignments. (In a many-to-one
>or many-to-many implementation the policy would be different of
>course.)  In other words, fix all those bugs before they occur, and
>transparent to the user of the abstraction.

Couldn't agree with you more.

[snip]

>  > Interesting interpretation.  I didn't think there was any trouble
>  > with returning "null".  Clients could just interpret a "null"
>  > value as meaning that E isn't associated with any Target.  But
>  > that's okay -- you just have a slightly different abstraction in
>  > mind than I have.

>   Major difference, but subtle in its way.  I hid the "pointer"
>types, so all external interfaces deal with objects, not pointers to
>objects.  Yes, we know that what gets passed around is a reference,
>so there is no extra calling overhead, 

In that case, it is quite a major difference.  My assumption was that
a "Get" operation would return a value that gave complete access to
the associated object as a *variable*, so that a client doing a
traversal would then be able to invoke operations that could modify
the state of that object, if need be.  In other words, the return
value would be a "name" that designated an object, rather than the
"value" (state) of object itself.  Since it is possible that there
might not yet be any object assigned along a given association (e.g.,
a particular Employee might not have an Office yet), it is possible
that the "name" retrieved by a Get would be "null" (or the moral
equivalent of that), designating "no object at all."

Now, I may be confused, but I always thought that a function call
could only act as the name of a *constant*, not the name of a
variable.  The only way the return result of a function call could
designate a variable would be if the return type were actually an
access type (or something readily translatable into one, such as one
of my Identity.Value types).  Has this changed in Ada9X?  From what
I can find in the RM9X;5.0, it seems not.

Unless your "object" types themselves act as the moral equivalent of
access types, magically designating your objects rather than being the
objects themselves, I think that the usefulness of your Get functions
will be very restricted.  In other words, your tagged types would have
to implement "reference semantics", in a way similar what Bill
Beckwith does in his IDL-to-Ada9X translation.  But if we're dealing
with reference semantics anyway, then the special value "reference to
no object at all" ought to be part of that semantics.

Taking a closer look at your code, I notice that your object types are
non-limited tagged types, rather than limited tagged types.  I thought
Ada9X could only guarantee pass-by-reference for a limited type, whereas
a non-limited type may be either pass-by-copy or pass-by-reference.

>but doing it this way
>eliminates--if done correctly--dangling pointer worries for users of
>the abstractions.  I didn't show all of that, but for example, the
>finalization operation on a Person would unset his office.  So there
>is no null value to return.

I'm not sure what the issue of "dangling pointers" has to do with the
choice of whether to hide the use of pointer types or expose the
possibility of null pointers.  I always thought that "dangling
pointers" referred to a situation where one object has already been
destroyed (e.g., via Unchecked_Deallocation), yet some other object
still has an access value designating it.  Certainly, the process of
finalizing any object should include disconnecting it from every
association it takes part in, so that the associated objects will no
longer retain pointers to it.  But doesn't that mean that those other
objects will then contain null pointers (assuming the cardinality is
"one")?

>					Robert I. Eachus

					John G. Volan

--------------------------------------------------------------------------------
--  Me : Person := (Name                => "John Volan",
--                  Company             => "Raytheon Missile Systems Division",
--                  E_Mail_Address      => "jgv@swl.msd.ray.com",
--                  Affiliation         => "Enthusiastic member of Team Ada!",
--                  Humorous_Disclaimer => "These opinions are undefined " &
--                                         "by my employer and therefore " &
--                                         "any use of them would be "     &
--                                         "totally erroneous.");
--------------------------------------------------------------------------------



  reply	other threads:[~1994-10-31 17:34 UTC|newest]

Thread overview: 45+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
1994-10-12 22:49 SOLVED! Decoupled Mutual Recursion Challenger John Volan
1994-10-17 15:48 ` John Volan
1994-10-17 17:55   ` Bob Duff
1994-10-17 20:52     ` John Volan
1994-10-17 22:10       ` Bob Duff
1994-10-18 22:17         ` John Volan
1994-10-19  1:01           ` Bob Duff
1994-10-19  4:45             ` Jay Martin
1994-10-19 14:38               ` Mark A Biggar
     [not found]                 ` <38fi4r$l81@oahu.cs.ucla.edu>
1994-10-24 11:49                   ` Mutual Recursion Challenge Robert I. Eachus
1994-10-24 20:32                     ` John Volan
1994-10-26 11:42                       ` Generic association example (was Re: Mutual Recursion Challenge) Robert I. Eachus
1994-10-26 23:21                         ` John Volan
1994-10-27 10:53                           ` Robert I. Eachus
1994-10-31 17:34                             ` John Volan [this message]
1994-10-27 14:37                           ` Mark A Biggar
1994-10-24 17:42                   ` SOLVED! Decoupled Mutual Recursion Challenger John Volan
1994-10-24 22:37                     ` Jay Martin
1994-10-25  5:47                       ` Matt Kennel
1994-10-25 10:04                         ` David Emery
1994-10-25 16:43                         ` John Volan
1994-10-27  4:25                           ` Rob Heyes
1994-10-28  9:03                             ` Mutual Recursion (was Re: SOLVED! Decoupled Mutual Recursion Challenger) Robert I. Eachus
1994-10-28 15:04                             ` SOLVED! Decoupled Mutual Recursion Challenger Robb Nebbe
1994-10-25 15:54                       ` John Volan
1994-10-26  1:24                         ` Bob Duff
1994-10-28  4:28                         ` Jay Martin
1994-10-28 10:52                           ` Robert I. Eachus
1994-10-28 18:46                             ` Jay Martin
1994-11-02 14:56                               ` Robert I. Eachus
1994-10-29  0:38                           ` Bob Duff
1994-10-29  7:26                             ` Jay Martin
1994-10-29 11:59                             ` Richard Kenner
1994-10-31 13:17                               ` Robert Dewar
1994-10-31 14:13                               ` gcc distribution (was: SOLVED! Decoupled Mutual Recursion Challenger) Norman H. Cohen
1994-11-02 14:14                                 ` Richard Kenner
1994-11-04 23:56                                   ` Michael Feldman
1994-10-31 18:44                           ` SOLVED! Decoupled Mutual Recursion Challenger John Volan
1994-10-20 11:25               ` Robb Nebbe
1994-10-20 19:19                 ` John Volan
1994-10-26  0:07                 ` Mark S. Hathaway
1994-10-26 18:48                 ` gamache
1994-10-27  2:15                   ` John Volan
     [not found]           ` <CxwGJF.FwB@ois.com>
1994-10-19 16:35             ` John Volan
1994-10-17 22:54   ` Cyrille Comar
replies disabled

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox