comp.lang.ada
 help / color / mirror / Atom feed
From: Matthew Heaney <matthew_heaney@acm.org>
Subject: Re: "out" or "access"
Date: 1998/11/02
Date: 1998-11-02T00:00:00+00:00	[thread overview]
Message-ID: <m31znmkgir.fsf@mheaney.ni.net> (raw)
In-Reply-To: wccn26bf4b0.fsf@world.std.com

Robert A Duff <bobduff@world.std.com> writes:

> > > Perhaps you should have two different sorts of iterators, one that can
> > > modify, and one that can't.
> > 
> > Yes, I thought of that too, but I was trying to keep things simple.
> 
> Too simple?

Ah, the process of design -- something about which I'm sure you are
familiar.

I'm designing a data structure library --which I've been calling the Ada
Components Library-- which comprises pluggable units.  You assemble the
data structure having the space & time characteristics you desire, from
a simpler set of primitives.  (Sound like our favorite language?)

This avoids the combinatorial explosion the plagued the original Booch
components.  He had to build tens and tens of data structures,
anticipating every combination.

The ACL isn't like that.  It's very simple -- only a few hundred lines.
(Actually, what made it all possible is composition via access
discriminants.)

Every data structure allows you to traverse that data structure one
element at a time.  Typically, there are two ways to do this - passive
iteration and active iteration.

From time-to-time, it's desirable to change the values of the items in
the data structure, without resorting to inefficient "solutions" like
copying the original data structure, and changing the source values
prior to insertion into the target structure.  You just want to change
the values in place.

You could use a passive iterator to do this.  The data structure is
passed inout, and the Process procedure passed as a generic formal takes
each item as mode inout.

You therefore have to provide two kinds of passive iterators: a
read-only version, and a read-write version.  The former is necessary
because you could be in a subprogram (say, a Print routine) that takes
the data structure as mode in.

So now things are starting to get overwhelming, if you consider that I
have to provide possibly two kinds of active iterators too.

My desire is to keep the library as simple as possible.  By providing
primitive components, the client can assemble those primitives together
to effect the desired behavior.  You don't have to everything for him.

We can keep the library simpler by providing only an active iterator,
and letting the client assemble a passive iterator himself (by using the
active iterator).  But if there are two kinds of passive iterators, that
means that either 1) I have to provide two active iterators, one for
read-only, and one for read-write, or 2) provide one active iterator
that can be used in both read-only or read-write contexts.

Obviously, my choice is for one active iterator, that allows you to
modify data structure elements, but that can be used anywhere, including
those places where the data structure is Officially read-only.

It may seem like using Addr_To_Acc_Conv for this is a "dirty trick," but
the client is going to build an infrastructure on top of the primitives,
and it's that ultimate data structure that is going to allow only
"legal" things.  For example, here's our two passive iterators:

   generic
      with procedure Process (Item : in Item_Type);
   procedure Select_Items (Stack : in Stack_Type);

   generic
      with procedure Process (Item : in out Item_Type);
   procedure Modify_Items (Stack : in out Stack_here's);

There's nothing "illegal" here: items can be selected from a read-only
stack, and can be modified from a read-write stack.  The client allows
only meaningful behavior for his stack.

We can use one active iterator to build both kinds of passive iterators:

   procedure Select_Items (Stack : in Stack_Type) is

      Iter : Stack_Iterator := Start_At_Top (Stack'Access);
   begin
      for I in 1 .. Get_Length (Stack) loop
         Process (Get_Item (Iter));
         Advance (Iter);
      end loop;
   end;


   procedure Modify_Items (Stack : in out Stack_Type) is

      Iter : Stack_Iterator := Start_At_Top (Stack'Access);
   begin
      for I in 1 .. Get_Length (Stack) loop
         Process (Get_Item (Iter));
         Advance (Iter);
      end loop;
   end;


As you can see, these have the same body.  Only one active iterator type
is required, in order to effect both kinds of ultimate behavior. 

Is that _too_ simple?  My philosophy is, less is more.


> But this allows clients to write upon constants, which sounds like a Bad
> Thing.

All of these data structures are tagged limited private.  They can't
ever be constant, can they?


> > The "const" thing is the entity passed as the in-param of the function
> > call.  In my case, the entities are always tagged, and so are aliased,
> > and so taking the address is well-defined.
> 
> But writing upon constants is not.

What's your defination of "constant"?  Is a (limited) by-reference
object passed as an in parameter a constant?  That's the only kind of
constant I was talking about.

Actually, I think Ada goes too far wrt to parameter modes.  For an
abstract data type, the arguments to subprograms should describe only a
logical view of the abstraction; what the client sees.

But internally, the implementor of an abstraction should be able to
change the state of the object as a side-effect of the call -- even if
the parameter mode is in.  It's his business how to implement his
abstraction.

In Ada, the outside view of the abstraction tends to constrain the
inside view too.  In a sense, by specifying the mode (in vs inout) for
the object of the abstract data type, you've overspecified.  By passing
the arg as inout --just so you can produce a side-effect legally--
you've exposed an aspect of the implementation, that isn't necessarily
important to clients.

That's why the "pure" object-oriented notation:

   Object.Do_Something

is nice, because it allows the implementor to make whatever state
changes are required in order to effect logical behavior, without having
to announce whether state changes are in fact required.  It's not the
client's business to know.


> > Essentially, I'm trying to get around Ada's lack of in out params for
> > functions.  How does the Ada programmer implement his own abstractions
> > whose functions have state-changing behavior -- like Random does?
> 
> I wish functions allowed 'in out' params.
> 
> In the absence of that, you can do the Random trick, but that's kind of
> messy.  I'd suggest an explicit access type instead.

For my Set_Top function, that's what I did:

   function Set_Top (Stack : access Stack_Type) return Item_Access;
...

   Set_Top (Stack'Access).all := 42;

What's motivating this is that I can write a stack that works for
limited private items.  Ever needed a stack of File_Types?  Something
like:

   Stack : access Stack_Type; -- of File_Type
...
   declare
      File : File_Type renames
         Push (Stack'Access).all;
   begin
      Create (File, Name => "matt.dat");
...
 
   declare
      File : File_Type renames
         Set_Top (Stack'Access).all;
   begin
      Close (File);
      Pop (Stack);
   end;


> > I should have asked, How do the language designers intend Ada programmers
> > to implement a side-effect producing function, whose param (of mode in)
> > is a tagged type?
> 
> I think the language designers intend that you shouldn't do that.

Then the intentions of the language designers are unclear, because of
function Random.

Programmers are going expect that they can do that kind of thing too,
efficiently, and within the language.


> > I would be really swell if we had constructors too, not just functions
> > trying to do the job.  The latter technique doesn't work for limited
> > types.
> 
> That was my point -- *initializing* a limited object with a function
> call should not be illegal.  It's assigning them around after that that
> causes trouble.  Likewise, an aggregate of a limited type could make
> perfect sense, if it's only used to *initialize* an object.

Yes!




  reply	other threads:[~1998-11-02  0:00 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
1998-10-21  0:00 "out" or "access" =:-) Vincent
1998-10-21  0:00 ` Jeff Carter
1998-10-21  0:00   ` Pat Rogers
1998-10-21  0:00     ` Martin C. Carlisle
1998-10-22  0:00       ` Pat Rogers
1998-10-22  0:00     ` Robert A Duff
1998-10-21  0:00 ` Tucker Taft
1998-10-22  0:00   ` Pascal Obry
1998-10-29  0:00     ` Robert A Duff
1998-10-29  0:00       ` Matthew Heaney
1998-10-29  0:00         ` Robert A Duff
1998-10-30  0:00           ` dennison
1998-10-30  0:00             ` Matthew Heaney
1998-10-30  0:00               ` Robert A Duff
1998-10-31  0:00                 ` dewar
1998-10-31  0:00                   ` Matthew Heaney
1998-10-31  0:00                 ` Matthew Heaney
1998-11-01  0:00                   ` Robert A Duff
1998-11-01  0:00                     ` Matthew Heaney
1998-11-01  0:00                       ` Robert A Duff
1998-11-02  0:00                         ` Matthew Heaney [this message]
1998-11-03  0:00                           ` Simon Wright
1998-11-16  0:00                             ` Matthew Heaney
1998-10-21  0:00 ` dennison
replies disabled

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