From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,9a7e0c43216f4def X-Google-Attributes: gid103376,public From: Matthew Heaney Subject: Re: "out" or "access" Date: 1998/11/02 Message-ID: X-Deja-AN: 407487032 Sender: matt@mheaney.ni.net References: <908956499.754394@dedale.pandemonium.fr> <70mo3h$gll$1@cf01.edf.fr> <71cjab$ka8$1@nnrp1.dejanews.com> NNTP-Posting-Date: Mon, 02 Nov 1998 00:39:11 PDT Newsgroups: comp.lang.ada Date: 1998-11-02T00:00:00+00:00 List-Id: Robert A Duff 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!