CVS difference for ais/ai-30302.txt

Differences between 1.7 and version 1.8
Log of other versions for file ais/ai-30302.txt

--- ais/ai-30302.txt	2004/09/04 01:13:47	1.7
+++ ais/ai-30302.txt	2004/09/14 01:25:58	1.8
@@ -17140,7 +17140,848 @@
 ****************************************************************
 
 From: Matthew Heaney
-Sent: Friday, July 2, 2004  9:31 AM
+Sent: Thursday, September  9, 2004  9:14 PM
+
+> -----Original Message-----
+> From: Randy Brukardt [mailto:randy@rrsoftware.com] 
+> Sent: Tuesday, September 07, 2004 1:13 PM
+> 
+> Of course, we don't have the Hashed_Set and Ordered_Map 
+> containers, and they will come up in practice. I'm now 
+> convinced that their omission was a mistake. I recently had a 
+> case where I could have used a map, but creating a decent 
+> hash function was going to be substantial work not justified 
+> by the number of elements to be used. Clearly, an Ordered_Map 
+> would be much easier to use in such a case. The examples 
+> given by Tucker are good examples of the use of a Hashed_Set.
+
+
+As a sort of thought experiment, I implemented a hashed set.  It's up at the
+CVS repository.
+
+<http://charles.tigris.org/source/browse/charles/src/ai302/a-cohase.ads?rev=
+HEAD&sortby=date&content-type=text/vnd.viewcvs-markup>
+
+<http://charles.tigris.org/source/browse/charles/src/ai302/a-cohase.ads?sort
+by=date#dirlist>
+
+<http://charles.tigris.org/source/browse/charles/src/ai302/?sortby=date#dirl
+ist>
+
+<http://charles.tigris.org/>
+
+It looks just like the ordered set, except for the generic formal region,
+and the addition of Set_Capactity, etc.
+
+****************************************************************
+
+From: Randy Brukardt
+Sent: Thursday, September 9, 2004  9:56 PM
+
+The problem, of course, is that the description of the operations in for
+the standard would be different, and its very late to be adding anything
+significant.
+
+I would hope that most implementers would provide a Hashed_Set patterned on
+your package. (This is the sort of thing that an IWA would be very good for.)
+
+****************************************************************
+
+From: Matthew Heaney
+Sent: Friday, September 11, 2004  3:09 AM
+
+I just uploaded an ordered map.
+
+http://charles.tigris.org/source/browse/charles/src/ai302/a-coorma.ads?rev=H
+EAD&sortby=date&only_with_tag=HEAD&content-type=text/vnd.viewcvs-markup
+
+http://charles.tigris.org/source/browse/charles/src/ai302/a-coorma.ads
+
+****************************************************************
+
+From: Nick Roberts
+Sent: Sunday, September 12, 2004 10:18 AM
+
+I think this proposal is looking pretty polished now.
+
+I hope it won't seem wrong for me to put my own responses to Randy's
+questions (to the ARG, I guess) at the end of the latest update to this AI.
+Apologies if the quoting seems heavy.
+
+> Q1) Find_Index returns Last_Index (Container) + 1 if the element is not
+> found. This seems consistent to me (it's past the end of the container in
+> a forward search), but Matt worries that First_Index (Container) - 1
+> might be thought of as better. The trouble with First_Index (Container) -
+> 1 is that you can't put it into an object:
+>    declare
+>         I : Index_Type := Index_Type'First;
+>     begin
+>         I := Find_Index (Vect, Item, I);
+>         while I <= Last_Index (Vect) loop
+>             -- Do something to the element I.
+>             I := Find_Index (Vect, Item, I+1);
+>         end loop;
+>     end;
+>If Find_Index returned Index_Type'First - 1, saving the result of
+> Find_Index would raise Constraint_Error if the item is not found. That's
+> not what we want, I think.
+
+The problem with Last_Index (Container) + 1 is that it may not exist,
+because Last_Index (Container) might be Index_Type'Last. On the other hand,
+we convenienty have a requirement that Index_Type'First >
+Index_Type'Base'First, which guarantees that First_Index (Container) - 1
+does always exist (as a value of Index_Type'Base).
+
+I think that swings it. I suggest Find_Index returns First_Index
+(Container) - 1 when it does not find what it is looking for.
+
+It might be convenient to declare two extra things in the
+Containers.Vectors package:
+
+    subtype Find_Index_Result is
+       Index_Type'Base range Index_Type'First-1 .. Index_Type'Last;
+
+    Not_Found: constant Find_Index_Result := Find_Index_Result'First;
+
+Obviously the Find_[Reverse_]Index functions can then have
+Find_Index_Result as their return types.
+
+The example can then be reformulated as:
+
+    declare
+       I : Find_Index_Result := Index_Subtype'First;
+    begin
+       I := Find_Index (Vect, Item, I);
+       while I /= Not_Found loop
+          -- Do something to the element I.
+          I := Find_Index (Vect, Item, I+1);
+       end loop;
+    end;
+
+or alternatively:
+
+    declare
+       I : Find_Index_Result := Index_Subtype'First - 1;
+    begin
+       loop
+          I := Find_Index (Vect, Item, I+1);
+          exit when I = Not_Found;
+          -- Do something to the element I.
+       end loop;
+    end;
+
+> Q2) The parameters to Generic_Merge have not been made class-wide (even
+> though the comments about non-primitive operations with specific tagged
+> parameters mentioned for Generic_Sort hold here, too). That's because
+> both parameters need to be the same type. An alternative would be to make
+> them class-wide, and then have a runtime check (of the actual tags) that
+> they actually are the same type. But that is not very O-O. A third
+> possibility would be to repeat the type in the generic spec:
+>   generic
+>        type List_Type is new List with private;
+>        with function "<" (Left, Right : Element_Type)
+>           return Boolean is <>;
+>     procedure Generic_Merge (Target : in out List_Type;
+>                              Source : in out List_Type);
+>But that is not very consistent with the rest of the specification. Some
+> guidance would be helpful here.
+
+I'm uncomfortable with Generic_Sort having a classwide parameter:
+
+    generic
+       with function "<" (Left, Right : Element_Type) return Boolean is <>;
+    procedure Generic_Sort (Container : in out List'Class);
+
+This is because the actual sorting operation can only be on objects of the
+root type, both conceptually and actually. The implementation would have to
+typecast the parameter Container to List anyway.
+
+I think it would make much more sense for Generic_Sort to be declared:
+
+    generic
+       with function "<" (Left, Right : Element_Type) return Boolean is <>;
+    procedure Generic_Sort (Container : in out List);
+
+and the typecast to be done in the call instead. For example:
+
+    package Float_Lists is new Ada.Containers.Doubly_Linked_Lists(Float);
+    procedure Sort is new Float_Lists.Generic_Sort;
+    type My_List is new Float_Lists.List with ...;
+    ...
+    L1: My_List;
+    ...
+    Sort( Float_Lists.List(L1) );
+
+I prefer this because it makes it explicit that the list L1 is being
+sorted /as/ a Float_Lists.List (not as a My_List, as such).
+
+To extend the example a bit further:
+
+    procedure Merge is new Float_Lists.Generic_Merge;
+    ...
+    type Freds_List is new Float_Lists.List with ...;
+    ...
+    L2: Freds_List;
+    ...
+    Merge( Float_Lists.List(L2), Float_Lists.List(L1) );
+
+I think this is the right formulation, since again it makes it explicit
+that you are merging the two lists /as/ Float_Lists.Lists (which is what
+makes them compatible enough to merge anyway).
+
+As is rightly pointed out, the implementations of Generic_Sort and
+Generic_Merge cannot use dispatching operations of their parameters
+(lists), so they must both be specific to their root types anyway. For this
+reason, I would suggest neither should have a classwide parameter.
+
+I hope I haven't totally missed the point here!
+
+Note also that there is a typo (lines 1418-1419): in the package
+specification itself the parameters to Generic_Merge are both still cited
+as List'Class.
+
+> Q6) Tucker has mentioned that he often has components in the key of a map
+> beyond the actual key participating ones. (This is similar to the
+> behavior of a set; if we had a Hashed_Set this probably would be less of
+> an issue.) For that to be effective, it would be necessary to change a
+> key that is already in a map. Currently, neither Replace_Element nor
+> Insert_or_Replace change the value of a key that is in the map; only the
+> element is changed.
+>In order to get the sort of semantics that Tucker seems to be suggesting,
+> we'd need a way to change the value of a key. But such an operation would
+> potentially change the location of the element, so it could be fairly
+> expensive. Moreover, it would likely require allocation even if the hash
+> didn't change for the indefinite form of the container.
+>Finally, whether or not the key is replaced would seem to be another
+> (orthogonal) option for the Insert routine "6) Insert replaces the key
+> and the element when the key is already in the map; 7) Insert replaces
+> the key, leaving the element unchanged when the key is already in the
+> map".
+>This complication doesn't seem worth it to me, but as it came up very
+> late, the entire ARG needs to discuss the issue.
+
+I think this is a case of "Don't do that!"
+
+Isn't the basic idea that key values are there specifically to provide fast
+indexed access to the elements? I don't think they are not intended to be
+used to carry ancillary information.
+
+It may sometimes be convenient to use an existing type (which has
+non-participating components) as a key type, but I think, in these cases,
+the non-participating components of the key should be moved into the
+element. If you have type T1 (with non-participating components) you want
+to use as a key for type T2, I think the proper design is to declare a new
+type T3, with only participating components, and a new type T4 which has
+the remaining components of type T1 and all those of T2 (or alternatively
+two components, of type T1 and T2). You then use T3 to index T4 (from which
+you extract the components of T1 or T2 as required).
+
+So, I agree that a key replacement operation should not be added.
+
+> Q3) The generic formal part for maps has:
+>   with function "=" (Left, Right : Key_Type)
+>       return Boolean is <>;
+>    with function Is_Equal_Key (Left, Right : Key_Type)
+>       return Boolean is "=";
+>Matt wonders why both operations are needed; [etc.]
+
+I hate this difference. I think it is counter-intuitive that keys have two
+different kinds of equality, and the reason why relates to my answer to Q6:
+I think the principle we should stick to is that the purpose of key values
+is solely to provide fast indexed access to a set of element values. On
+this priciple, I think it is intuitively the case that the equality that is
+implied by the ordering operation on the keys:
+
+    (not A<B) and (not B<A) |- A=B
+
+is the one and only equality of keys. (I use |- for semantic entailment.)
+
+I therefore favour dropping one of the equality operations.
+
+I'd like one equality operation, to be named "=", having the role currently
+fulfilled by Is_Equal_Key, and also used for the map equality test.
+
+To obtain the functionality required in the example -- that two maps,
+identical except for one entry which has key="46" in one map and key="0046"
+in the other (but "46"="0046" according to the generic "=" function), are
+considered unequal -- one could include a duplicate of the key as a
+component of the element type.
+
+> Q4) Set_Capacity is defined to raise Constraint_Error if
+> Length (Container) > Count. Matt would prefer that this case is not
+> separately handled. He would like
+>    Set_Capacity (M, 0)
+>to be a shorthand for setting the Map or Vector to the smallest
+> reasonable size. (I find this a bit odd, as Matt never wanted this
+> routine to even allow smaller values. But whatever.) Note that just
+> dropping the check would not be enough; we'd have to redo the description
+> of the operation to say that the capacity is set to at least
+> Count_Type'Max (Count, Length(Container)) -- because we don't want this
+> operation to drop elements. I'm unsure that the benefit is worth the
+> change, and it seems like a bug to me to try to set the capacity of a
+> container to be smaller than the number of elements it holds.
+
+The suggested semantics could only be a convenience. The suggested example
+could always be replaced by:
+
+    Set_Capacity( M, Length(M) );
+
+I don't think Set_Capacity is going to be used very often, in practice. I
+believe that on the (relatively few) occasions when a reviewer or
+maintainer saw:
+
+    Set_Capacity (Widgets, 0);
+
+after actions which clearly caused Widgets to contain something, there
+would be a significant risk of confusion and misunderstanding. This code
+might be mistakenly removed, on the assumption it was faulty.
+
+I think this is a case where readability is more important than conciseness
+of code, and I think using an explicit length will usually be more
+readable. At least, I think its meaning is a bit more intuitively obvious.
+
+~~~
+
+Q5 was about using an extra parameter of an enumerated type in insertion
+and replacement procedures to indicate what should happen if the key exists
+(insertion) or doesn't exist (replacement), with a default indicating that
+an exception should be raised.
+
+I rather prefer this idea. I think it is the least of three evils (no user
+choice, a plethora of procedures, or control-coupling).
+
+~~~
+
+Another question that arises in my mind is, for any of the functions which
+returns a container: what is the capacity of the result? In particular,
+should this be defined by the standard, or remain implementation defined?
+[Call this Q7?]
+
+****************************************************************
+
+From: Matthew Heaney
+Sent: Monday, September 13, 2004  12:56 AM
+
+Each comment immediately follows the text to which it refers, and is
+bracketed with "MJH:" and "ENDMJH." pairs.
+
+Some of these items are already on the agenda for Madison, so for those
+items I'll just summarize the issue.
+
+
+Doubly-linked lists:
+
+   Empty_List : constant List;
+   --NOTE: function or deferred constant?
+
+MJH:
+
+We can get rid of this note in the spec, since we now know that Empty_List
+is a deferred constant.
+
+ENDMJH.
+
+
+   generic
+      with function "<" (Left, Right : Element_Type)
+         return Boolean is <>;
+   procedure Generic_Merge (Target  : in out List'Class;
+                            Source  : in out List'Class);
+
+MJH:
+
+We need to resolve the declaration of the parameter types in Madison.  We
+can either declare the two types as class-wide (as above), or import the
+list as a generic formal type, like this:
+
+   generic
+      type List_Type is new List with private;
+      with function "<" (Left, Right : Element_Type)
+         return Boolean is <>;
+   procedure Generic_Merge (Target  : in out List_Type;
+                            Source  : in out List_Type);
+
+Note also that the description of this operation uses type List (probably
+just a typo).
+
+Other possibilities for List_Type are:
+
+   type List_Type (<>) is new List with private;
+  
+or maybe:
+
+   type List_Type (<>) is abstract new List with private;
+
+ENDMJH.
+
+
+procedure Insert (Container : in out List;
+                  Before    : in     Cursor;
+                  New_Item  : in     Element_Type;
+                  Count     : in     Count_Type := 1);
+
+Insert allocates Count new nodes whose element is initialized to the value
+New_Item, and inserts them prior to the node designated by Before. If
+Before equals No_Element, the new nodes are inserted immediately
+following the last node (if any). Any exception raised during allocation of
+internal storage is propagated, and Container is not modified.
+
+MJH:
+
+We have to decide whether partial success is allowed.  (The last sentence
+above looks vestigial, and might have been written prior to the introduction
+of the Count parameter.)
+
+For example, if Count is 10, and we're only able to allocate, say, 7 nodes,
+then can the list be modified such that its length only grew by 7 nodes?
+
+ENDMJH.
+
+
+procedure Swap (Container : in out List;
+                I, J      : in     Cursor);
+
+Swap exchanges the nodes designated by I and J.
+
+AARM Note: Unlike Swap_Elements for vectors, this exchanges the nodes, not
+the
+elements. No copying is performed. I and J designate the same elements after
+this call as they did before it. This is important, as this operation is
+provided as it can provide better performance than a straight copying swap.
+The
+programmer can writing a copying swap if they need one. This difference in
+semantics is the reason that this operations have different names in the
+List
+and Vector containers.
+
+MJH:
+
+The penultimate sentence should say:
+
+"The programmer can write a copying swap if he needs one."
+
+We also need to specify the behavior when one or both of the parameters
+equal No_Element.
+
+It probably wouldn't hurt anything to add a Swap_Elements operation, too.
+If an implementor uses pointers to elements for the indefinite form, than at
+least that would confer a performance benefit (the same as for the
+indefinite vector).
+
+ENDMJH.
+
+
+Hashed maps:
+
+
+generic
+
+   type Key_Type is private;
+
+   type Element_Type is private;
+
+   with function Hash (Key : Key_Type) return Hash_Type;
+
+   with function "=" (Left, Right : Key_Type)
+      return Boolean is <>;
+
+   with function Is_Equal_Key (Left, Right : Key_Type)
+      return Boolean is "=";
+
+   with function "=" (Left, Right : Element_Type)
+      return Boolean is <>;
+
+package Ada.Containers.Hashed_Maps is
+
+MJH:
+
+We need to resolve the characteristics of this generic formal region in
+Madison.
+
+Note that in Palma we decided to use the name "Equivalent" instead of
+"Is_Equal_Key".  
+
+ENDMJH.
+      
+
+   procedure Query_Element
+       (Position : in Cursor;
+        Process  : not null access procedure (Element : in Element_Type));
+
+   procedure Update_Element
+      (Position : in Cursor;
+       Process : not null access procedure (Element : in out Element_Type));
+
+MJH:
+
+We might need some other operations, if we're serious about manipulating
+(and possibly modifying) keys.  Here are some ideas:
+
+   procedure Query_Key
+       (Position : in Cursor;
+        Process  : not null access procedure (Key : in Key_Type));
+
+   procedure Query_Key_And_Element
+       (Position : in Cursor;
+        Process  : not null access procedure (Key : in Key_Type;
+                                              Element : in Element_Type));
+
+   procedure Query_Key_And_Update_Element
+      (Position : in Cursor;
+       Process : not null access procedure (Key : in Key_Type;
+                                            Element : in out Element_Type));
+
+   procedure Checked_Update_Key
+      (Container : in out Map;
+       Position  : in Cursor;
+       Process   : not null access procedure (Key : in out Key_Type));
+
+ENDMJH.                                            
+
+
+   procedure Insert_Or_Replace (Container : in out Map;
+                                Key       : in     Key_Type;
+                                New_Item  : in     Element_Type);
+
+MJH:
+
+Tucker thought that this operation replaced the value of the key too, so we
+need to confirm the exact semantics of this operation.
+
+ENDMJH.
+
+
+
+   function Is_Equal_Key (Left, Right : Cursor)
+      return Boolean;
+
+   function Is_Equal_Key (Left  : Cursor;
+                          Right : Key_Type)
+      return Boolean;
+
+   function Is_Equal_Key (Left  : Key_Type;
+                          Right : Cursor)
+      return Boolean;
+
+MJH:
+
+We need to decide about these ops in Madison: either change the name, or
+keep the name, or get rid of them.
+
+ENDMJH.
+
+
+procedure Set_Capacity (Container : in out Map;
+                        Capacity  : in     Count_Type);
+
+If Length (Container) > Capacity, then Constraint_Error is propagated.
+Otherwise, Set_Capacity allocates a new hash table such that the length of
+the
+resulting map can become at least the value Capacity without requiring an
+additional Set_Capacity operation. If the allocation fails, the exception is
+propagated and Container is not modified. It then rehashes the nodes in
+Container onto the new hash table. It replaces the old hash table with the
+new
+hash table, and then deallocates the old hash table.
+
+MJH:
+
+(This comment applies to vector, too.)  
+
+We have already discussed the fact that I don't think raising CE is
+appropriate for this operation.  
+
+The purpose of an exception is to indicate that the postcondition cannot be
+satisfied.  If for example, a very large value for Capacity were requested,
+and the implemention were unable to allocate the requisite storage, then an
+exception would be appropriate.
+
+However, the postcondition here is "Capacity(Container) >= Capacity", and an
+invariant is "Capacity(Container) >= Length(Container)".  If Capacity <
+Length(Container), then the implementation will allocate a capacity that is
+at least the container's length, thus satisfying both the postcondition and
+the invariant.  Therefore an exception is not appropriate.
+
+Note that the STL member function reserve() (which is equivalent to
+Set_Capacity) does *not* raise an exception.
+
+ENDMJH.
+
+
+Ordered Sets:
+
+
+   function Is_Disjoint (Left, Right : Set) return Boolean;
+
+MJH:
+
+I had originally chosen the name Is_Disjoint to be consistent with Is_In.
+This was also the name favored by John Barnes.  However, Is_In has since
+been renamed to Contains.  Should we consider a similar name change for
+Is_Disjoint?
+
+   function Overlaps (Left, Right : Set) return Boolean;
+
+This is the name Tucker prefers.
+
+ENDMJH.   
+
+
+   generic
+
+      type Key_Type (<>) is limited private;
+
+      with function Key (Element : Element_Type) return Key_Type;
+
+      with function "<" (Left : Key_Type; Right : Element_Type)
+          return Boolean is <>;
+
+      with function ">" (Left : Key_Type; Right : Element_Type)
+          return Boolean is <>;
+
+   package Generic_Keys is
+
+MJH:
+
+We have to make a decision about whether we want to pass the set type as a
+generic actual type, like this:
+
+   generic
+      type Set_Type is new Set with private;
+      type Key_Type (<>) is limited private;
+      ..
+
+and then use Set_Type everywhere for operations.  Or instead declare the
+container parameters as type Set'Class.
+
+Actually, another declaration might be:
+
+   type Set_Type (<>) is new Set with private;
+
+or possibly:
+
+   type Set_Type (<>) is abstract new Set with private;
+
+(However we declare Set_Type, we should do the same for List_Type of
+Generic_Merge.)
+
+ENDMJH.      
+
+
+       procedure Checked_Update_Element
+          (Container : in Set;
+           Position  : in Cursor;
+           Process   : not null access procedure (Element : in out
+Element_Type));
+
+MJH:
+
+The Container param should be inout, not in.  (I think you already fixed
+this.)
+
+ENDJH.
+
+
+
+!examples
+
+Ordered Sets:
+
+Another technique would be to use an active iterator, like this:
+
+   procedure Shutdown_Connections is
+      I : Cursor;
+      X : Connection_Access;
+   begin
+      while not Is_Empty (Connection_Set) loop
+         I := First (Connect_Set);
+         X := Element (I);
+         Delete (Connection_Set, Position => I);
+         Free (X);
+      end loop;
+   end Shutdown_Connections;
+
+Here we use the cursor-form of Delete.  This is probably more efficient
+than using the item-form of Delete, since the cursor-form doesn't have
+to search for the item.
+
+MJH:
+
+We might also want to say that this example can be simplified as follows:
+
+START EXAMPLE TEXT
+
+The example can be simplified by using the set operations that 
+manipulate the first element specifically:
+
+   procedure Shutdown_Connections is
+      X : Connection_Access;
+   begin
+      while not Is_Empty (Connection_Set) loop
+         X := First_Element (Connect_Set);
+         Delete_First (Connection_Set);
+         Free (X);
+      end loop;
+   end Shutdown_Connections;
+
+END EXAMPLE TEXT
+
+ENDMJH.
+
+
+To actually change the employee's address in the example above, we use
+the special element modifier operation:
+
+   procedure Change_Address
+     (SSN      : SSN_Type;
+      New_Home : Home_Address_Type) is
+
+      procedure Set_Home
+        (Employee : in out Employee_Type) is
+      begin
+         Employee.Home := New_Home;
+      end;
+
+      Position : Cursor := Find (Employees, Key => SSN);
+   begin
+      if Has_Element (Position) then
+         SSN_Keys.Checked_Update_Element
+            (Position  => Position,
+             Process   => Set_Home'Address);
+         ...
+      end if;
+   end Change_Address;
+
+MJH:
+
+The call to Checked_Update_Element needs to pass the set, too:
+
+      SSN_Keys.Checked_Update_Element
+         (Container => Employees,
+          Position  => Position,
+          Process   => Set_Home'Address);
+
+ENDMJH.
+             
+
+
+Another technique is to use Checked_Update_Element, which allows the
+element's key to be modified, and then moves then element to its new
+relative location in the set:
+
+   procedure Change_SSN
+     (Old_SSN : SSN_Type;
+      New_SSN : SSN_Type) is
+
+      Old_Position, New_Position : Cursor;
+      Inserted                   : Boolean;
+   begin
+      if New_SSN = Old_SSN then
+        return;
+      end if;
+
+      Old_Position := Find (Employees, Key => Old_SSN);
+
+      if not Has_Element (Old_Position) then
+        return;
+      end if;
+
+      New_Position := Find (Employees, Key => New_SSN);
+
+      if Has_Element (New_Position) then
+         raise Duplicate_SSN;
+      end if;
+
+      declare
+         procedure Set_SSN (Employee : in out Employee_Type) is
+         begin
+            Employee.SSN := New_SSN;
+         end;
+      begin
+         SSN_Keys.Checked_Update_Element
+           (Position  => Old_Position,
+            Process   => Set_SSN'Access);
+      end;
+   end Change_SSN;
+
+MJH:
+
+Same thing here: we need to pass the set as a parameter:
+
+         SSN_Keys.Checked_Update_Element
+           (Container => Employees,
+            Position  => Old_Position,
+            Process   => Set_SSN'Access);
+
+ENDMJH.            
+   
+
+Suppose now we want a list all the employees in the firm. One way to do
+it is like this:
+
+   procedure Display is
+
+      procedure Print (I : in Employee_Sets.Cursor) is
+
+         procedure Do_Print (E : in out Employee_Type) is
+         begin
+            Put ("Name: ");  Put (E.Name);
+            Put ("SSN: "); Put (E.SSN);
+            ...;
+         end;
+
+      begin
+        Query_Element (Position => I, Process => Do_Print'Access);
+      end;
+
+   begin
+      Iterate (Employees, Print'Access);
+   end;
+
+MJH:
+
+The mode for the (generic) actual Do_Print should be just in, not inout.
+
+ENDMJH.
+   
+
+   begin
+
+      Sort (Cursors);
+
+      for Index in Cursors'Range loop
+          C := Cursors (Index);
+          Query_Element (Position => C, Process => Do_Print'Access);
+      end loop;
+
+   end Display_Employees_In_Name_Order;
+
+MJH:
+
+All of the process procedures above need to change the mode from in to
+inout.
+
+ENDMJH.   
+
+
+This lets us perform session lookups based on the session identifier:
+
+   procedure Play
+     (Session_Id  : in     String;
+      NPT_Range   : in     NPT_Range_Type;
+      RTSP_Status :    out RTSP_Status_Type) is
+
+      Position : constant Session_Set_Types.Cursor :=
+        Find (Session_Set, Key => Session_Id);
+
+MJH:
+
+We might want to use name qualification here:
+
+      Position : constant Session_Set_Types.Cursor :=
+        Id_Keys.Find (Session_Set, Key => Session_Id);
+
+ENDMJH.
 
 ****************************************************************
 

Questions? Ask the ACAA Technical Agent