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: Wed, 26 Oct 1994 23:21:54 GMT
Date: 1994-10-26T23:21:54+00:00	[thread overview]
Message-ID: <1994Oct26.232154.29094@swlvx2.msd.ray.com> (raw)
In-Reply-To: EACHUS.94Oct26114221@spectre.mitre.org

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

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

> > One problem I see with this particular formulation is that a
> > Person-who-can-occupy-an-Office can point to any Office, not
> > necessarily only an Office-that-can-be-occupied; likewise, an
> > Office-that-can-be-occupied can point to any Person, not
> > necessarily only a Person-who-can-occupy-an-Office.

>That is what abstract types are for.  In particular, the feature added
>last August that abstract types need not have any abstract operations
>makes it easy.  All the types that you can point to but shouldn't are
>abstract, and since the type determination in a dispacthing operation
>comes from the object, the issue doesn't arise.

Hmmm... Yes, I can see that this guarantees that the only objects that
would exist at run-time would be "Persons-who-can-occupy-Offices" and
"Offices-that-can-be-occupied-by-Persons", and not just "Persons" and
"Offices".  Or, more to the point, they could only be "Total Persons"
and "Total Offices", supporting all the associations that pertain to
"Persons" and "Offices" within the given application.  And the
objects themselves would "know" what concrete type they were, by
virtue of their tags.

But I still see a problem: It looks like each class is giving up the
ability to use static strong typing to assert the exact type of the
*other* objects associated with it.  In other words, a "Person-who-
can-occupy-an-Office" can't assert that it's associated with an
"Office-that-can-be-occupied-by-a-Person" (or with a "Total Office",
for that matter).  All it can say is that it's associated with an
"Office".  So when a client asks a Person object for its Office, the
Person can answer with its associated "Office", but it can't provide
the client with a "Total" view of that Office.  The client would have
to do the view-conversion itself by "narrowing" (safely downcasting)
that Office into a "Total Office".

That narrowing operation would incur a run-time check on the Office's
tag.  But that check is actually redundant.  You know and I know that
the Office object will really be a "Total Office"; we can assert that
as a statically-determined fact, since we're the designers of the
software, and that was our original intent for the association.
However, when we go to write our Ada code for the Person class, we
discover that we can't explicitly assert, in the Ada code itself, that
a Person's associated Office will actually be one of those "Total
Offices".  So we can't tell the compiler that this is a
statically-determined assertion that it can check statically.
Instead, it becomes a dynamically-checked assertion.  In short, this
takes us a step away from a statically-checked style of programming,
and moves us closer to the dynamically-checked style of languages such
as Smalltalk.

I suppose we could apply a pragma Suppress at some point to eliminate
the extra dynamic check, once the program was fully tested.  But, more
to the point, we've lost a certain amount of expressibility.  Since
the Ada code doesn't directly express what we mean, its readability by
other Ada programmers (for example, maintainers) is reduced.  

Moreover, I must insist once again that all these extra layers of
abstract types don't really represent anything from the problem
domain.  As far as our object-oriented analysis is concerned, a Person
is just a Person, and an Office is just an Office.  Part of what it
means to be a Person is the ability to occupy an Office, and likewise,
part of being an Office is the ability to accomodate Person (at least
within the application in question).  All those abstract types layered
on top of this are really just artifacts of the way we've chosen to
construct the software that will implement our analysis.  Perhaps we
can exploit all these layered views to gain some reusability benefits,
but the costs that they incur are very clear: The job of reading and
understanding the program has been complicated by the proliferation of
multiple views for what, in the problem domain, are just single
classes.

You mentioned that you found it difficult to come up with *names* for
those multiple views.  I've had trouble with that too, as you can see
from the way I had to resort to awkward phrases such as
"Person-who-can-occupy-an-Office".  Far from being a minor
inconvenience, I really think this is a diagnostic symptom of a very
serious problem.  What cannot be easily named, cannot be easily
explained, and therefore cannot be easily understood.

> > Is there a way of putting this together that guarantees the
> > invariant that if a Person occupies an Office, then that Office is
> > occupied by that Person?

>Yes.  It is best understood with the "double generic" version, but
>doesn't depend on it.  However, make sure you reserve one-to-one
>mappings for where they are appropriate.  Supporting many-to-many
>mappings requires a lot more complexity in the Association
>abstraction, so there should probably be several--one-to-one and onto,
>one-to-many, and many-to-many.  They only need to be written once, so
>let's try the simplest case:

>(I spent a lot of time barking up the wrong tree on this.  Querying
>the attributes is not a problem, but defining an operation to set them
>resulted in all sorts of visible kludges or silly looking code.  There
>are two facets to the solution.  The first is that in the one-to-one
>case there are two necessary set operations: un-set and set, not set
>for office and set for person.  

Yes, I encountered the same issue: I had to have a "Dissociate"
operation as well as an "Associate" operation, and the latter wound up
calling the former when the cardinality of the association was "one"
(but not when it was "many").

>The second is that, while the query
>functions want to be class-wide in one parameter, [the Set procedures] 
>should be
>symmetric, and thus class-wide in both.)

That's if you're interpreting "Set" and "Unset" (or "Associate" and
"Dissociate") as "friend" operations.  In other words, you're not
considering them as "belonging" to either class specifically, but
rather they're sort of straddling the association between the classes.

But that's not the only way you could arrange things.  If you noticed,
in my previous posts, I actually conceived of *distributing* this
"Setting" responsibility to *both* of the classes.  In other words, I
imagined having *two* "Associate" operations: one that was primitive
for an Employee and one that was primitive for an Office.  The
Associate for Employee would only directly modify the Employee's
pointer to its Office; likewise the Associate for Office would only
directly modify the Office's pointer to its Employee.  That follows
the "Law of Demeter": an object's operations should only directly
manipulate its own attributes, but not the attributes of any other
object.

However, both of these Associate operations would also be responsible
for satisfying the invariant of the association: i.e, if an Employee
points to an Office, then that Office must point back to that Employee
as well.  The most convenient way for the two Associate operations to
do that would be ...  to call each other!  In other words, they would
be *mutually recursive* subprograms, reflecting the *mutually
recursive* relationship between the two classes.

Consequently, we could use either one of those Associate subprograms
to establish a link between a given Employee and a given Office.
Which one we would pick at any given point would be arbitrary, and
would probably depend on which of the two classes we were focusing on
in the course of some larger algorithm.

The upside to this scheme is that we didn't have to resort to breaking
object-oriented encapsulation in order to implement this capability.
In other words, we didn't have to make this a "friend" operation that
can "see" the structure of both types at once.  The downside, though,
is that now we have to look at two subprograms, distributed in two
packages, to fully understand how the "functionality" of "setting up
an association" will get done.  But I don't think that's really so
bad.  As I've said before, it's going to be a very common thing for
"functionality" to be smeared across multiple object classes, whenever
you make the objects, rather than the functions, the central criterion
of your software design.

>    generic
>      type Father is abstract tagged private;
>       -- probably all the abstract types should be limited too.
>      type Target_Ancestor is abstract tagged private;
>      -- ancester of the destination type, for example, Controlled.
>    package Association is

>      type Extended is abstract new Father with private;

>      function Get(E: in Extended) return Target_Ancestor'CLASS;
                                           ^^^^^^^^^^^^^^^
You see, that's what I mean -- you've lost the ability to express the
exact type of the other object in a static fashion, even though you,
as the designer, do know what that exact class should be, a priori.

>      -- If you follow the above directions about abstraction, this
>      -- must always return the "right" type.  But if you have several
>      -- non-abstract types which are specializations of say Person,
>      -- you want the attribute declared this way anyway.  

Yes, I have no trouble with this returning a class-wide type, I just
have trouble with the root-type of the class being so indistinct.  I'd
rather be able to say something like "Internal_Extended'Class" here.
But that's the chicken-and-egg problem again.

>      -- Raises
>      -- Constraint_Error if the attribute is not set. 

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.

>      function Is_Set(E: in Extended) return Boolean;
>      -- Inquiry function to avoid Constraint_Error.

This might be supplied anyway as a convenience, even if you go with
my interpretation of allowing Get to return null.

>      generic
>        type Mother is abstract tagged private;
>      package Inner_Association is
>         
>        type Inner_Extended is new Mother with private;

>        function Get(E: in Inner_Extended) return Extended'CLASS;
>        -- Again we want the 'CLASS even in cases where it may not be
>        -- necessary to complete the code...

>        function Is_Set(E: in Extended) return Boolean;
>        -- Inquiry function to avoid Constraint_Error as above.

>        procedure Safe_Set (E:   in out Extended'CLASS;
>                            IE:  in out Inner_Extended'CLASS);

>        procedure Force_Set(E:   in out Extended'CLASS;
>                            IE:  in out Inner_Extended'CLASS);

>        -- There are two choices here, set in any case, but preserve
>        -- the invariants, or raise an exception and change nothing if
>        -- one or the other is already set.  Since it is simple to
>        -- provide both, I do so.  (Safe_Set does the checks and may
>        -- raise an exception, Force_Set unsets the partner of any
>        -- object that is being reassigned.)

Interesting.  I hadn't thought of providing these alternatives.  My
Associate subprograms correspond to your Force_Set.

>        procedure UnSet(E: in out Extended'CLASS);
>        procedure UnSet(IE: in out Extended'CLASS);
>        -- UnSet the attribute.  If already set, unset the partner as well.

By the way, in the case of "many"-cardinality, I think UnSet would
actually wind up being a single operation with two parameters.

>      private

>        type Outer_Ref is access all Extended;

>        type Inner_Extended is new Mother with record
>          Attribute: Outer_Ref;
>        end record;

>      end Inner_Association;

>      pragma INLINE(Get, Is_Set, Safe_Set, Force_Set);

>    private

>      type Inner_Ref is access all Target_Ancestor;

>      type Extended is new Father with record
>        Attribute: Inner_Ref;
>      end record;

>      pragma INLINE(Get, Is_Set);

>    end Association;


[snip implementation of generic package body]


>      Okay, now using this package goes like this...

>      with Ada.Finalization; with Assignments;
>      package People is

>	type Base_Person is abstract 
>             new Ada.Finalization.Controlled with private;

>        package Office_Assignments is 
>                   new Assignments(Base_Person,Ada.Finalization.Controlled);
                                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

Once again, it looks like we've lost some expressibility here: At this
point, we can't say anything more about what kind of objects People
are going to be associated with, other than that they're going to be
something derived from Ada.Finalization.Controlled.  But you and I
know that these are Offices.

>      
>        type Person is new Office_Assignments.Extended with null;

>        function Office(P: in Person) return Controlled'CLASS renames Get;
>        function Has_Office(P: in Person) return Boolean renames Is_Set;

>      private
>        ...
>      end People;
>                 
>      with Ada.Finalization; with People;
>      package Offices is
>	type Office_Base is abstract new Ada.Finalization.Controlled
>                 with private;
>        package People_Assignments is new
>          People.Office_Assignments.Inner_Association(Office_Base);
>        
>        type Office is new People_Assignments.Inner_Extended with null;
>        function Occupant(O: in Office) return
>              People.Office_Assignments.Extended'CLASS renames Get;
>        function Is_Occupied(O: in Office) return Boolean renames Is_Set;
>        procedure Reassign(P: in out People.Office_Assignments.Extended'CLASS;
>                           O: in out People_Assignments.Inner_Extended'CLASS);
>        ...

>      private
>        ...
>      end Offices;

>  > If I'm totally mixed-up about mixins :-), please help me out.  Thanks.

>    I hope this helps.  The trick is to get as much of the "plumbing"
>code into generics which are written once, and then use appropriate
>renamings to make it understandable.  

I fully agree with this sentiment in principle, but not necessarily
with the way you've put it into practice here.  For example, those
mutually-recursive Associate procedures I described above do follow a
common pattern that can apply to any association.  That makes them an
excellent candidate for a generic solution, and I think I've come up
with one.  However, it only takes care of *implementing* the Associate
operations.  You still have to write the spec for an Associate
procedure in each of your class packages.  But when you get to the
package bodies, you don't have to worry about doing the "plumbing."
You can instantiate the template in terms of those procedure specs,
and then, via renaming declarations, you can use the resulting
instance procedures as the *bodies* for those specs!  Granted, it
doesn't take care of all the "boilerplating" that you need to support
an association; I think perhaps some code-generating tool might be
better suited to doing that, and this generic could could work along
with that tool. But using renaming declarations as subprogram bodies
is a neat trick that Ada9X now makes possible.

>(In fact, in the code above I
>probably would use subtype definitions to make those ugly 'CLASS
>parameters go away.  The other possible approach is to replace the
>renamings with operations on the parent types which do the ugly calls
>in the body.  It's a matter of style and in this case, I'm trying to
>show the workings...)
>--

>					Robert I. Eachus

>with Standard_Disclaimer;
>use  Standard_Disclaimer;
>function Message (Text: in Clever_Ideas) return Better_Ideas is...


-- John 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-26 23:21 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 [this message]
1994-10-27 10:53                           ` Robert I. Eachus
1994-10-31 17:34                             ` John Volan
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