!standard A.16 03-09-25 AI95-00302/07 !class amendment 02-06-13 !status work item 02-06-13 !status received 02-06-10 !priority Medium !difficulty Hard !subject Data structure components for Ada !summary Data structures are basic to the field of software engineering. Having a standard set of data structure components will be good for the language and its users. It is proposed that the data structure components of the PragmAda Reusable Components be adopted as the basis for a standard data structure library. These components have been designed to be as generally useful as possible; within that constraint they are also intended to be as easy to use a possible. !problem Data structures are basic to the field of software engineering; so basic that one of Wirth's books is titled @i. Ada has no standard data structure library (except for Ada.Strings), unlike many languages. Having a standard set of data structure components defined for the language will help promote the use of Ada, increase developer efficiency, and promote portability. !proposal Data structure components similar to those of the PragmAda Reusable Components are proposed as a standard data structure library. !wording A.X Data Structures This clause presents the specifications of the package Ada.Containers and several child packages, which provide abstract data types representing basic, common data structures. Lists, queues, stacks, bags, and skip lists are supported. A.X.1 The Package Ada.Containers The package Ada.Containers provides declarations common to the data-structures packages. Static Semantics The library package Ada.Containers has the following declaration: package Ada.Containers is pragma Pure; Empty_Structure_Error : exception; Full_Structure_Error : exception; Too_Short_Error : exception; end Ada.Containers; The exception Empty_Structure_Error is raised by operations when an attempt is made to access data in an empty structure. The exception Full_Structure_Error is raised by operations on bounded structures when an attempt is made to add data to a full structure. The exception Too_Short_Error is raised by the Assign operation for bounded structures if the destination structure does not have a large enough maximum size to hold the data in the source structure. The library package Ada.Containers.Lists has the following declaration: package Ada.Containers.Lists is pragma Pure; Position_Error : exception; end Ada.Containers.Lists; The exception Position_Error is raised by operations on lists if a position is not valid. A.X.2 Unprotected Bounded-Length List Handling The language-defined package Ada.Containers.List.Bounded_Unprotected provides a generic package each of whose instances yields a limited private type Bounded_List, a private type Position, and a set of operations. An object of a particular List_Bounded_Unprotected Bounded_List type represents a list whose length can vary conceptually between 0 and a maximum size established at the declaration of the object. Object of type Position are used to indicate values of type Element stored in a list. Static Semantics The library package Ada.Containers.List.Bounded_Unprotected has the following declaration: generic -- Ada.Containers.Lists.Bounded_Unprotected type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Containers.Lists.Bounded_Unprotected is pragma Preelaborate; type Bounded_List (Max_Size : Positive) is limited private; type Position is private; procedure Assign (To : out Bounded_List; From : in Bounded_List); procedure Clear (List : in out Bounded_List); -- Operations to obtain valid positions for lists: function First (List : Bounded_List) return Position; function Last (List : Bounded_List) return Position; function Off_List (List : Bounded_List) return Position; -- Operations to obtain valid positions from valid positions: function Next (Pos : Position; List : Bounded_List) return Position; function Prev (Pos : Position; List : Bounded_List) return Position; -- Operations to manipulate lists procedure Insert (Into : in out Bounded_List; Item : in Element; Before : in Position; New_Pos : out Position); procedure Append (Into : in out Bounded_List; Item : in Element; New_Pos : out Position); procedure Delete (From : in out Bounded_List; Pos : in out Position); function Get (From : Bounded_List; Pos : Position) return Element; procedure Put (Into : in out Bounded_List; Pos : in Position; Item : in Element); function Is_Empty (List : Bounded_List) return Boolean; function Is_Full (List : Bounded_List) return Boolean; function Length (List : Bounded_List) return Natural; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Pos : in Position; Continue : out Boolean); procedure Iterate (Over : in out Bounded_List; Context : in out Context_Data); private -- Ada.Containers.Lists.Bounded_Unprotected -- not specified by the language end Ada.Containers.Lists.Bounded_Unprotected; A list is a sequence of Elements. Each Element in the sequence may be accessed through a Position; we say that the Position indicates the Element. Elements may be added to, deleted from, accessed, or modified in a list at any position. Using First and Next, a list may be traversed from the first Element to the last; using Last and Prev, from the last Element to the first. An object of type Bounded_List is initially empty. An object of type Position is initially invalid. Bounded_List's discriminant, Max_Size, is the maximum number of Elements that may be stored in a list. procedure Assign (To : out Bounded_List; From : in Bounded_List); If To and From are the same list, Assign has no effect. If To.Max_Size < Length (From), Too_Short_Error is propagated. Otherwise, To is cleared and made into a copy of From. Precondition: To.Max_Size >= Length (From) Postcondition: Length (To) = Length (From) procedure Clear (List : in out Bounded_List); This procedure makes List empty. Postcondition: Is_Empty (List) Length (List) = 0 Note: Clear effectively deletes all nodes in a list. Therefore, Position_Error is propagated if a Position, obtained before Clear is called, is used to access the list after Clear is called. function First (List : Bounded_List) return Position; This function returns a Position indicating the first Element in List. If List is empty, this is the same as Off_List (List). function Last (List : Bounded_List) return Position; This function returns a Position indicating the last Element in List. If List is empty, this is the same as Off_List (List). function Off_List (List : Bounded_List) return Position; This function returns a Position that is valid for List but indicates no Element in List. This is the same Position returned by Next (Last (List), List) and by Prev (First (List), List). function Next (Pos : Position; List : Bounded_List) return Position; If Pos is not a valid Position for List, Position_Error is propagated. If Pos = Last (List), this function returns Off_List (List). If Pos = Off_List (List), this function returns First (List). Otherwise, this function returns a Position that indicates the next Element in List after the Element indicated by Pos. function Prev (Pos : Position; List : Bounded_List) return Position; If Pos is not a valid Position for List, Position_Error is propagated. If Pos = First (List), this function returns Off_List (List). If Pos = Off_List (List), this function returns Last (List). Otherwise, this function returns a Position that indicates the previous Element in List before the Element indicated by Pos. procedure Insert (Into : in out Bounded_List; Item : in Element; Before : in Position; New_Pos : out Position); If Into is full, Full_Structure_Error is propagated. If Before is not a valid Position for Into, Position_Error is propagated. If Into is empty and Before /= Off_List (Into), Position_Error is propagated. If Before = Off_List (List), Item is added as the last Element in Into. Otherwise, Item is inserted into Into before the Element indicated by Before. New_Pos becomes a valid Position for Into that indicates Item. Precondition: not Is_Full (Into) Postcondition: Prev (Before, Into) = New_Pos procedure Append (Into : in out Bounded_List; Item : in Element; New_Pos : out Position); If Into is full, Full_Structure_Error is propagated. If Into is empty, Item is added as the only item of the list; otherwise, Item is inserted into Into after the Last element. New_Pos becomes a valid Position for Into that indicates Item. Precondition: not Is_Full (Into) Postcondition: Last (Into) = New_Pos procedure Delete (From : in out Bounded_List; Pos : in out Position); If From is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for From or Pos = Off_List (From), Position_Error is propagated. Otherwise, the Element indicated by Pos is removed from From. Pos is made invalid. Elements are stored in "nodes" in a list. A Position indicates an Element by indicating its node. One Position may indicate a node that has been deleted through another Position. Position_Error is propagated if such a Position is subsequently used to access the list. Precondition: not Is_Empty (From) Postcondition: not Is_Full (From) function Get (From : Bounded_List; Pos : Position) return Element; If From is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for From or Pos = Off_List (From), Position_Error is propagated. Otherwise, this function returns the Element in From indicated by Pos. Precondition: not Is_Empty (From) procedure Put (Into : in out Bounded_List; Pos : in Position; Item : in Element); If Into is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for Into or Pos = Off_List (Into), Position_Error is propagated. Otherwise, the Element in Into indicated by Pos is made to be Item. Precondition: not Is_Empty (Into) function Is_Empty (List : Bounded_List) return Boolean; This function returns True if List is empty and False otherwise. function Is_Full (List : Bounded_List) return Boolean; This function returns True if List is full and False otherwise. function Length (List : Bounded_List) return Natural; This function returns the number of Elements stored in List. generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Pos : in Position; Continue : out Boolean); procedure Iterate (Over : in out Bounded_List; Context : in out Context_Data); An instance of this procedure applies Action to each Element stored in Over and its Position in turn, from first to last. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in Over. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a list while iterating over it. Possible consequences are: propagating Position_Error, propagating Constraint_Error, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. Implementation Permission An implementation may reuse deleted nodes, even if that means it cannot guarantee that Position_Error will be propagated when a Position that indicates a deleted node is used to access the list. A.X.3 Unprotected Unbounded-Length List Handling The language-defined package Ada.Containers.Lists.Unbounded_Unprotected provides a generic package each of whose instances yields a limited private type Unbounded_List, a private type Position, and a set of operations. An object of a particular List_Bounded_Unprotected Unbounded_List type represents a list whose length can vary conceptually between 0 and a maximum size established by available storage. Objects of type Position are used to indicate values of type Element stored in a list. The subprograms for bounded lists are overloaded for unbounded lists, except Is_Full. Static Semantics The library package Ada.Containers.Lists.Unbounded_Unprotected has the following declaration: generic -- Ada.Containers.Lists.Unbounded_Unprotected type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Containers.Lists.Unbounded_Unprotected is pragma Preelaborate; type Unbounded_List is limited private; type Position is private; procedure Assign (To : out Unbounded_List; From : in Unbounded_List); procedure Clear (List : in out Unbounded_List); -- Operations to obtain valid positions for lists: function First (List : Unbounded_List) return Position; function Last (List : Unbounded_List) return Position; function Off_List (List : Unbounded_List) return Position; -- Operations to obtain valid positions from valid positions: function Next (Pos : Position; List : Unbounded_List) return Position; function Prev (Pos : Position; List : Unbounded_List) return Position; -- Operations to manipulate lists procedure Insert (Into : in out Unbounded_List; Item : in Element; Before : in Position; New_Pos : out Position); procedure Append (Into : in out Unbounded_List; Item : in Element; New_Pos : out Position); procedure Delete (From : in out Unbounded_List; Pos : in out Position); function Get (From : Unbounded_List; Pos : Position) return Element; procedure Put (Into : in out Unbounded_List; Pos : in Position; Item : in Element); function Is_Empty (List : Unbounded_List) return Boolean; function Length (List : Unbounded_List) return Natural; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Pos : in Position; Continue : out Boolean); procedure Iterate (Over : in out Unbounded_List; Context : in out Context_Data); private -- Ada.Containers.Lists.Unbounded_Unprotected -- not specified by the language end Ada.Containers.Lists.Unbounded_Unprotected; A list is a sequence of Elements. Each Element in the sequence may be accessed through a Position; we say that the Position indicates the Element. Elements may be added to, deleted from, accessed, or modified in a list at any position. Using First and Next, a list may be traversed from the first Element to the last; using Last and Prev, from the last Element to the first. An object of type Unbounded_List is initially empty. An object of type Position is initially invalid. procedure Assign (To : out Unbounded_List; From : in Unbounded_List); If To and From are the same list, Assign has no effect. Otherwise, To is cleared and made into a copy of From. If, after clearing To, there is insufficient storage to allocate list nodes for copies of all the Elements in From, Storage_Error is propagated. Postcondition: Length (To) = Length (From) procedure Clear (List : in out Unbounded_List); This procedure makes List empty. Postcondition: Is_Empty (List) Length (List) = 0 function First (List : Unbounded_List) return Position; This function returns a Position indicating the first Element in List. If List is empty, this is the same as Off_List (List). function Last (List : Unbounded_List) return Position; This function returns a Position indicating the last Element in List. If List is empty, this is the same as Off_List (List). function Off_List (List : Unbounded_List) return Position; This function returns a Position that is valid for List but indicates no Element in List. This is the same Position returned by Next (Last (List), List) and by Prev (First (List), List). function Next (Pos : Position; List : Unbounded_List) return Position; If Pos is not a valid Position for List, Position_Error is propagated. If Pos = Last (List), this function returns Off_List (List). If Pos = Off_List (List), this function returns First (List). Otherwise, this function returns a Position that indicates the next Element in List after the Element indicated by Pos. function Prev (Pos : Position; List : Unbounded_List) return Position; If Pos is not a valid Position for List, Position_Error is propagated. If Pos = First (List), this function returns Off_List (List). If Pos = Off_List (List), this function returns Last (List). Otherwise, this function returns a Position that indicates the previous Element in List before the Element indicated by Pos. procedure Insert (Into : in out Unbounded_List; Item : in Element; Before : in Position; New_Pos : out Position); If Before is not a valid Position for Into, Position_Error is propagated. If Into is empty and Before /= Off_List (Into), Position_Error is propagated. If there is insufficient storage to allocate a new list node, Storage_Error is propagated. If Before = Off_List (List), Item is added as the last Element in Into. Otherwise, Item is inserted into Into before the Element indicated by Before. New_Pos becomes a valid Position for Into that indicates Item. Postcondition: Prev (Before, Into) = New_Pos procedure Append (Into : in out Unbounded_List; Item : in Element; New_Pos : out Position); If there is insufficient storage to allocate a new list node, Storage_Error is propagated. If Into is empty, Item is added as the first Element in Into. Otherwise, Item is inserted into Into after the Last element. New_Pos becomes a valid Position for Into that indicates Item. Postcondition: Last (Into) = New_Pos procedure Delete (From : in out Unbounded_List; Pos : in out Position); If From is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for From or Pos = Off_List (From), Position_Error is propagated. Otherwise, the Element indicated by Pos is removed from From. Pos is made invalid. Elements are stored in "nodes" in a list. A Position indicates an Element by indicating its node. One Position may indicate a node that has been deleted through another Position. Position_Error is propagated if such a Position is subsequently used to access the list. Clear effectively deletes all nodes in a list. Therefore, Position_Error is propagated if a Position, obtained before Clear is called, is used to access the list after Clear is called. Precondition: not Is_Empty (From) function Get (From : Unbounded_List; Pos : Position) return Element; If Pos is not a valid Position for From or Pos = Off_List (From), Position_Error is propagated. Otherwise, this function returns the Element in From indicated by Pos. Precondition: not Is_Empty (From) procedure Put (Into : in out Unbounded_List; Pos : in Position; Item : in Element); If Into is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for Into or Pos = Off_List (Into), Position_Error is propagated. Otherwise, the Element in Into indicated by Pos is made to be Item. Precondition: not Is_Empty (Into) function Is_Empty (List : Unbounded_List) return Boolean; This function returns True if List is empty and False otherwise. function Length (List : Unbounded_List) return Natural; This function returns the number of Elements stored in List. generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Pos : in Position; Continue : out Boolean); procedure Iterate (Over : in out Unbounded_List; Context : in out Context_Data); An instance of this procedure applies Action to each Element stored in Over and its Position in turn, from first to last. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in Over. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a list while iterating over it. Possible consequences are: propagating Position_Error, propagating Constraint_Error, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. Implementation Permission An implementation may reuse deleted nodes, even if that means it cannot guarantee that Position_Error will be propagated when a Position that indicates a deleted node is used to access the list. Implementation Requirements No storage associated with an Unbounded_List object shall be lost upon scope exit, a call to Clear or Delete, or being associated with the parameter To of a call to Assign. A.X.4 Unprotected Unbounded Bag Handling The language-defined package Ada.Containers.Bag_Unbounded_Unprotected provides a generic package each of whose instances yields a limited private type Unbounded_Bag and a set of operations. An object of a particular Bag_Unbounded_Unprotected Unbounded_Bag type represents a bag whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Containers.Bag_Unbounded_Unprotected has the following declaration: generic -- Ada.Containers.Bag_Unbounded_Unprotected type Element is private; with function "=" (Left : Element; Right : Element) return Boolean is <>; package Ada.Containers.Bag_Unbounded_Unprotected is pragma Preelaborate; type Unbounded_Bag is limited private; procedure Assign (To : out Unbounded_Bag; From : in Unbounded_Bag); procedure Clear (Bag : in out Unbounded_Bag); procedure Add (Item : in Element; Into : in out Unbounded_Bag); procedure Delete (Item : in Element; From : in out Unbounded_Bag); procedure Update (Item : in Element; Bag : in out Unbounded_Bag); type Find_Result (Found : Boolean := False) is record case Found is when False => null; when True => Item : Element; end case; end record; function Find (Key : Element; Bag : Unbounded_Bag) return Find_Result; function Is_Empty (Bag : Unbounded_Bag) return Boolean; function Size (Bag : Unbounded_Bag) return Natural; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Unbounded_Bag; Context : in out Context_Data); private -- Ada.Containers.Bag_Unbounded_Unprotected -- not specified by the language end Ada.Containers.Bag_Unbounded_Unprotected; A bag is an unordered collection of Elements. Elements stored in a bag may be searched for, updated, and deleted if they match a user-supplied Element according to the generic formal function "=". Actual functions supplied for "=" often compare only part of an Element (the "key") to obtain their result. An object of type Unbounded_Bag is initially empty. procedure Assign (To : out Unbounded_Bag; From : in Unbounded_Bag); If To and From are the same bag, Assign has no effect. Otherwise, To is cleared and made into a copy of from. If, after clearing To, there is insufficient storage for copies of all the Elements in From, Storage_Error is propagated. Postcondition: Size (To) = Size (From) procedure Clear (Bag : in out Unbounded_Bag); Clear makes Bag empty. Postcondition: Is_Empty (Bag) Size (Bag) = 0 procedure Add (Item : in Element; Into : in out Unbounded_Bag); If there is insufficient storage available to store Item in From, Storage_Error is propagated. Otherwise, Add adds Item to Into. Postcondition: not Is_Empty (Into) Size (Into) > 0 procedure Delete (Item : in Element; From : in out Unbounded_Bag); If From contains an Element X such that X = Item, Delete deletes X from From. Otherwise, Delete has no effect. If From contains more than one such Element, Delete deletes one of these Elements. Which of these Elements is deleted is undefined. procedure Update (Item : in Element; Bag : in out Unbounded_Bag); If Bag contains an Element X such that X = Item, Update conceptually performs X := Item. Otherwise, Update has no effect. If Bag contains more than one such Element, Update updates one of these Elements. Which of these Elements is updated is undefined. Type Find_Result defines the results of function Find. function Find (Key : Element; Bag : Unbounded_Bag) return Find_Result; If Bag contains an Element X such that X = Key, Find returns (Found => True, Item => X). Otherwise, Find returns (Found => False). If Bag contains more than one such Element, Find returns one of these Elements as the Item component of the result. Which of these Elements is returned is undefined. function Is_Empty (Bag : Unbounded_Bag) return Boolean; Is_Empty returns True if Bag contains no elements and False otherwise. function Size (Bag : Unbounded_Bag) return Natural; Size returns the number of elements stored in Bag. generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Unbounded_Bag; Context : in out Context_Data); An instance of Iterate applies Action to each Element in Over. The order in which the Elements in Over are processed is undefined. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in Over. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a bag while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. Implementation Requirements No storage associated with an Unbounded_Bag object shall be lost upon scope exit, a call to Clear or Delete, or being associated with the parameter To of a call to Assign. AARM Note This package could be implemented using an instantiation of Ada.Containers.Lists.Unbounded_Unprotected. A.X.5 Unprotected Bounded Queue Handling The language-defined package Ada.Containers.Queue_Bounded_Unprotected provides a generic package each of whose instances yields a limited private type Bounded_Queue and a set of operations. An object of a particular Queue_Bounded_Unprotected Bounded_Queue type represents a queue whose size can vary conceptually between 0 and a maximum size established at the declaration of the object. Static Semantics The library package Ada.Containers.Queue_Bounded_Unprotected has the following declaration: generic -- Ada.Containers.Queue_Bounded_Unprotected type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Containers.Queue_Bounded_Unprotected is pragma Preelaborate; type Bounded_Queue (Max_Size : Positive) is limited private; procedure Clear (Queue : in out Bounded_Queue); procedure Assign (To : out Bounded_Queue; From : in Bounded_Queue); procedure Put (Into : in out Bounded_Queue; Item : in Element); procedure Get (From : in out Bounded_Queue; Item : out Element); function Is_Full (Queue : Bounded_Queue) return Boolean; function Is_Empty (Queue : Bounded_Queue) return Boolean; function Length (Queue : Bounded_Queue) return Natural; function Peek (Queue : Bounded_Queue) return Element; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Bounded_Queue; Context : in out Context_Data); private -- Ada.Containers.Queue_Bounded_Unprotected -- not specified by the language end Ada.Containers.Queue_Bounded_Unprotected; A queue is an ordered collection of Elements. Elements are added to the tail of a queue and removed from the head of the queue. A queue is a first in, first out (FIFO) data structure. An object of type Bounded_Queue is initially empty. procedure Clear (Queue : in out Bounded_Queue); Clear makes Queue empty. Postcondition: Is_Empty (Queue) Length (Queue) = 0 procedure Assign (To : out Bounded_Queue; From : in Bounded_Queue); If To and From are the same queue, Assign has no effect. If To.Max_Size < Length (From), Too_Short_Error is propagated. Otherwise, To is cleared and made into a copy of From. Precondition: To.Max_Size >= Length (From) Postcondition: Length (To) = Length (From) procedure Put (Into : in out Bounded_Queue; Item : in Element); If Into is full, Full_Structure_Error is propagated. Otherwise, Put adds Item to the tail of Into. Precondition: not Is_Full (Into) Postcondition: not Is_Empty (Into) Length (Into) > 0 procedure Get (From : in out Bounded_Queue; Item : out Element); If From is empty, Empty_Structure_Error is propagated. Otherwise, the Element at the head of From is removed from From and put in Item. Precondition: not Is_Empty (From) Postcondition: not Is_Full (From) function Is_Full (Queue : Bounded_Queue) return Boolean; Is_Full returns True if Queue is full and False otherwise. function Is_Empty (Queue : Bounded_Queue) return Boolean; Is_Empty returns True if Queue is empty and False otherwise. function Length (Queue : Bounded_Queue) return Natural; Length returns the number of Elements stored in Queue function Peek (Queue : Bounded_Queue) return Element; If Queue is empty, Empty_Structure_Error is propagated. Otherwise, Peek returns the Element at the head of Queue without altering Queue. Precondition: not Is_Empty (Queue) generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Bounded_Queue; Context : in out Context_Data); An instance of this procedure applies Action to each Element stored in Over in turn, from head to tail. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in Over. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a queue while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. AARM Note This package could be implemented using an instantiation of Ada.Containers.Lists.Bounded_Unprotected. A.X.6 Protected Bounded Queue Handling The language-defined package Ada.Containers.Queue_Bounded provides a generic package each of whose instances yields a protected type Bounded_Queue with a set of operations. An object of a particular Queue_Bounded_Protected Bounded_Queue type represents a queue whose size can vary conceptually between 0 and a maximum size established at the declaration of the object. The client must also specify the ceiling priority of the protected object at its declaration. Static Semantics The library package Ada.Containers.Queue_Bounded has the following declaration: with System; generic -- Ada.Containers.Queue_Bounded type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Containers.Queue_Bounded is pragma Preelaborate; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Continue : out Boolean); protected type Bounded_Queue (Max_Size : Positive; Ceiling_Priority : System.Any_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; procedure Put (Item : in Element); procedure Get (Item : out Element); function Is_Full return Boolean; function Is_Empty return Boolean; function Length return Natural; function Peek return Element; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Bounded_Queue -- not specified by the language end Bounded_Queue; end Ada.Containers.Queue_Bounded; An object of type Bounded_Queue represents the same abstraction as an object of type Ada.Containers.Queue_Bounded_Unprotected.Bounded_Queue, but protected to allow access by multiple tasks. An object of type Bounded_Queue is initially empty. procedure Clear; Clear makes the queue empty Postcondition: Is_Empty procedure Put (Item : in Element); If the queue is full, Full_Structure_Error is propagated. Otherwise, Put adds Item to the tail of the queue. Precondition: not Is_Full Postcondition: not Is_Empty procedure Get (Item : out Element); If the queue is empty, Empty_Structure_Error is propagated. Otherwise, the Element at the head of the queue is removed from the queue and put in Item. Precondition: not Is_Empty Postcondition: not Is_Full function Is_Full return Boolean; Is_Full returns True if the queue is full and False otherwise. function Is_Empty return Boolean; Is_Empty returns True if the queue is empty and False otherwise. function Length return Natural; Length returns the number of Elements stored in the queue function Peek return Element; If the queue is empty, Empty_Structure_Error is propagated. Otherwise, Peek returns the Element at the head of the queue without altering the queue. Precondition: not Is_Empty procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the queue in turn, from head to tail. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the queue. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the queue. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a queue while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in the queue, processing some of the Elements twice, and normal processing. Implementation Permission An implementation may add additional declarations to the specification of Ada.Containers.Queue_Bounded, including a private part for the package, as needed to complete the private part of protected type Bounded_Queue. AARM Note This package could be implemented using an instantiation of Ada.Containers.Queue_Bounded_Unprotected. A.X.7 Protected Blocking Bounded Queue Handling The language-defined package Ada.Containers.Queue_Bounded_Blocking provides a generic package each of whose instances yields a protected type Bounded_Queue with a set of operations. An object of a particular Queue_Bounded_Protected Bounded_Queue type represents a queue whose size can vary conceptually between 0 and a maximum size established at the declaration of the object. The client must also specify the ceiling priority of the protected object at its declaration. Static Semantics The library package Ada.Containers.Queue_Bounded_Blocking has the following declaration: with System; generic -- Ada.Containers.Queue_Bounded_Blocking type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Containers.Queue_Bounded_Blocking is pragma Preelaborate; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Continue : out Boolean); protected type Bounded_Queue (Max_Size : Positive; Ceiling_Priority : System.Any_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; entry Put (Item : in Element); entry Get (Item : out Element); function Is_Full return Boolean; function Is_Empty return Boolean; function Length return Natural; function Peek return Element; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Bounded_Queue -- not specified by the language end Bounded_Queue; end Ada.Containers.Queue_Bounded; An object of type Bounded_Queue represents the same abstraction as an object of type Ada.Containers.Queue_Bounded.Bounded_Queue, but Put and Get are entries and block the caller until the queue becomes non-full and non-empty, respectively. An object of type Bounded_Queue is initially empty. procedure Clear; Clear makes the queue empty Postcondition: Is_Empty entry Put (Item : in Element); If the queue is full, the caller is blocked until the queue becomes non-full. When the queue is not full, Put adds Item to the tail of the queue. Barrier: not Is_Full Postcondition: not Is_Empty entry Get (Item : out Element); If the queue is empty, the caller is blocked until the queue becomes non-empty. When the queue is not empty, the Element at the head of the queue is removed from the queue and put in Item. Barrier: not Is_Empty Postcondition: not Is_Full function Is_Full return Boolean; Is_Full returns True if the queue is full and False otherwise. function Is_Empty return Boolean; Is_Empty returns True if the queue is empty and False otherwise. function Length return Natural; Length returns the number of Elements stored in the queue function Peek return Element; If the queue is empty, Empty_Structure_Error is propagated. Otherwise, Peek returns the Element at the head of the queue without altering the queue. Precondition: not Is_Empty procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the queue in turn, from head to tail. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the queue. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the queue. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a queue while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in the queue, processing some of the Elements twice, and normal processing. Implementation Permissions An implementation may add additional declarations to the specification of Ada.Containers.Queue_Bounded_Blocking, including a private part for the package, as needed to complete the private part of protected type Bounded_Queue. The barriers of Put and Get must have the same effect as the specified barriers, but do not have to be identical to them. AARM Note This package could be implemented using an instantiation of Ada.Containers.Queue_Bounded_Unprotected. A.X.8 Unprotected Unbounded Queue Handling The language-defined package Ada.Containers.Queue_Unbounded_Unprotected provides a generic package each of whose instances yields a limited private type Unbounded_Queue and a set of operations. An object of a particular Queue_Unbounded_Unprotected Unbounded_Queue type represents a queue whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Containers.Queue_Unbounded_Unprotected has the following declaration: generic -- Ada.Containers.Queue_Unbounded_Unprotected type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Containers.Queue_Unbounded_Unprotected is pragma Preelaborate; type Unbounded_Queue (Max_Size : Positive) is limited private; procedure Clear (Queue : in out Unbounded_Queue); procedure Assign (To : out Unbounded_Queue; From : in Unbounded_Queue); procedure Put (Into : in out Unbounded_Queue; Item : in Element); procedure Get (From : in out Unbounded_Queue; Item : out Element); function Is_Empty (Queue : Unbounded_Queue) return Boolean; function Length (Queue : Unbounded_Queue) return Natural; function Peek (Queue : Unbounded_Queue) return Element; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Unbounded_Queue; Context : in out Context_Data); private -- Ada.Containers.Queue_Unbounded_Unprotected -- not specified by the language end Ada.Containers.Queue_Unbounded_Unprotected; An object of type Unbounded_Queue is initially empty. procedure Clear (Queue : in out Unbounded_Queue); Clear makes Queue empty. Postcondition: Is_Empty (Queue) Length (Queue) = 0 procedure Assign (To : out Unbounded_Queue; From : in Unbounded_Queue); If To and From are the same queue, Assign has no effect. Otherwise, To is cleared and made into a copy of From. If, after clearing To, there is insufficient storage for copies of all the Elements in From, Storage_Error is propagated. Postcondition: Length (To) = Length (From) procedure Put (Into : in out Unbounded_Queue; Item : in Element); If there is insufficient storage available to store Item in Into, Storage_Error is propagated. Otherwise, Put adds Item to the tail of Into. Postcondition: not Is_Empty (Into) Length (Into) > 0 procedure Get (From : in out Unbounded_Queue; Item : out Element); If From is empty, Empty_Structure_Error is propagated. Otherwise, the Element at the head of From is removed from From and put in Item. Precondition: not Is_Empty (From) Postcondition: not Is_Full (From) function Is_Empty (Queue : Unbounded_Queue) return Boolean; Is_Empty returns True if Queue is empty and False otherwise. function Length (Queue : Unbounded_Queue) return Natural; Length returns the number of Elements stored in Queue. function Peek (Queue : Unbounded_Queue) return Element; If Queue is empty, Empty_Structure_Error is propagated. Otherwise, Peek returns the Element at the head of Queue without altering Queue. Precondition: not Is_Empty (Queue) generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Unbounded_Queue; Context : in out Context_Data); An instance of this procedure applies Action to each Element stored in Over in turn, from head to tail. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the queue. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a queue while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. AARM Note This package could be implemented using an instantiation of Ada.Containers.Lists.Unbounded_Unprotected. A.X.9 Protected Unbounded Queue Handling The language-defined package Ada.Containers.Queue_Unbounded provides a generic package each of whose instances yields a protected type Unbounded_Queue with a set of operations. An object of a particular Queue_Unbounded_Protected Unbounded_Queue type represents a queue whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Containers.Queue_Unbounded has the following declaration: with System; generic -- Ada.Containers.Queue_Unbounded type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Containers.Queue_Unbounded is pragma Preelaborate; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Continue : out Boolean); protected type Unbounded_Queue (Ceiling_Priority : System.Any_Priority := System.Default_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; procedure Put (Item : in Element); procedure Get (Item : out Element); function Is_Empty return Boolean; function Length return Natural; function Peek return Element; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Unbounded_Queue -- not specified by the language end Unbounded_Queue; end Ada.Containers.Queue_Unbounded; An object of type Unbounded_Queue represents the same abstraction as an object of type Ada.Containers.Queue_Unbounded_Unprotected.Unbounded_Queue, but protected to allow access by multiple tasks. An object of type Unbounded_Queue is initially empty. procedure Clear; Clear makes the queue empty Postcondition: Is_Empty procedure Put (Item : in Element); If there is insufficient storage available to store Item in the queue, Storage_Error is propagated. Otherwise, Put adds Item to the tail of the queue. Postcondition: not Is_Empty procedure Get (Item : out Element); If the queue is empty, Empty_Structure_Error is propagated. Otherwise, the Element at the head of the queue is removed from the queue and put in Item. Precondition: not Is_Empty Postcondition: not Is_Full function Is_Empty return Boolean; Is_Empty returns True if the queue is empty and False otherwise. function Length return Natural; Length returns the number of Elements stored in the queue. function Peek return Element; If the queue is empty, Empty_Structure_Error is propagated. Otherwise, Peek returns the Element at the head of the queue without altering the queue. Precondition: not Is_Empty procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the queue in turn, from head to tail. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the queue. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the queue. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a queue while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in the queue, processing some of the Elements twice, and normal processing. Implementation Permission An implementation may add additional declarations to the specification of Ada.Containers.Queue_Unbounded, including a private part for the package, as needed to complete the private part of protected type Unbounded_Queue. AARM Note This package could be implemented using an instantiation of Ada.Containers.Queue_Unbounded_Unprotected. A.X.10 Protected Blocking Unbounded Queue Handling The language-defined package Ada.Containers.Queue_Unbounded_Blocking provides a generic package each of whose instances yields a protected type Unbounded_Queue with a set of operations. An object of a particular Queue_Unbounded_Protected Unbounded_Queue type represents a queue whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Containers.Queue_Unbounded_Blocking has the following declaration: with System; generic -- Ada.Containers.Queue_Unbounded_Blocking type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Containers.Queue_Unbounded_Blocking is pragma Preelaborate; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Continue : out Boolean); protected type Unbounded_Queue (Ceiling_Priority : System.Any_Priority := System.Default_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; procedure Put (Item : in Element); entry Get (Item : out Element); function Is_Empty return Boolean; function Length return Natural; function Peek return Element; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Unbounded_Queue -- not specified by the language end Unbounded_Queue; end Ada.Containers.Queue_Unbounded; An object of type Unbounded_Queue represents the same abstraction as an object of type Ada.Containers.Queue_Bounded_Blocking.Unbounded_Queue, except it is unbounded. An object of type Unbounded_Queue is initially empty. procedure Clear; Clear makes the queue empty Postcondition: Is_Empty procedure Put (Item : in Element); If there is insufficient storage available to store Item in the queue, Storage_Error is propagated. Otherwise, Put adds Item to the tail of the queue. Postcondition: not Is_Empty entry Get (Item : out Element); If the queue is empty, the caller is blocked until the queue becomes non-empty. When the queue is not empty, the Element at the head of the queue is removed from the queue and put in Item. Barrier: not Is_Empty Postcondition: not Is_Full function Is_Empty return Boolean; Is_Empty returns True if the queue is empty and False otherwise. function Length return Natural; Length returns the number of Elements stored in the queue function Peek return Element; If the queue is empty, Empty_Structure_Error is propagated. Otherwise, Peek returns the Element at the head of the queue without altering the queue. Precondition: not Is_Empty procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the queue in turn, from head to tail. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the queue. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the queue. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a queue while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in the queue, processing some of the Elements twice, and normal processing. Implementation Permissions An implementation may add additional declarations to the specification of Ada.Containers.Queue_Unbounded_Blocking, including a private part for the package, as needed to complete the private part of protected type Unbounded_Queue. The barrier of Get must have the same effect as the specified barrier, but does not have to be identical to it. AARM Note This package could be implemented using an instantiation of Ada.Containers.Queue_Unbounded_Unprotected. A.X.11 Discrete Set Handling The language-defined package Ada.Containers.Set_Bit_Mapped provides a generic package each of whose instances yields a limited private type Bit_Mapped_Set and a set of operations. An object of a particular Set_Bit_Mapped Bit_Mapped_Set type represents a set over a universe defined by the generic discrete formal type Element. Static Semantics The library package Ada.Containers.Set_Bit_Mapped has the following declaration: generic -- Ada.Containers.Set_Bit_Mapped type Element is (<>); package Ada.Containers.Set_Bit_Mapped is pragma Pure; type Bit_Mapped_Set is private; Empty : constant Bit_Mapped_Set; Full : constant Bit_Mapped_Set; function "+" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set; function Union (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set renames "+"; function "+" (Left : Bit_Mapped_Set; Right : Element) return Bit_Mapped_Set; function "+" (Left : Element; Right : Bit_Mapped_Set) return Bit_Mapped_Set; function Union (Left : Bit_Mapped_Set; Right : Element) return Bit_Mapped_Set renames "+"; function Union (Left : Element; Right : Bit_Mapped_Set) return Bit_Mapped_Set renames "+"; function "*" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set; function Intersection (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set renames "*"; function "-" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set; function Difference (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set renames "-"; function "-" (Left : Bit_Mapped_Set; Right : Element) return Bit_Mapped_Set; function Difference (Left : Bit_Mapped_Set; Right : Element) return Bit_Mapped_Set renames "-"; function "/" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set; function Symmetric_Difference (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set renames "/"; function "<=" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean; function Subset (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean renames "<="; function "<" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean; function Proper_Subset (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean renames "<"; function ">=" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean; function Superset (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean renames ">="; function ">" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean; function Proper_Superset (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean renames ">"; function Member (Item : Element; Set : Bit_Mapped_Set) return Boolean; type Member_List is array (Positive range <>) of Element; function Make_Set (List : Member_List) return Bit_Mapped_Set; function Size (Set : Bit_Mapped_Set) return Natural; private -- Ada.Containers.Set_Bit_Mapped -- not defined by the language end Ada.Containers.Set_Bit_Mapped; An object of type Bit_Mapped_Set is intially empty. The constant Bit_Mapped_Set Empty represents a set with no elements. The constant Bit_Mapped_Set Full represents a set with all possible elements. function "+" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set; function Union (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set renames "+"; "+" (Union) returns the union of its two parameters. function "+" (Left : Bit_Mapped_Set; Right : Element) return Bit_Mapped_Set; function "+" (Left : Element; Right : Bit_Mapped_Set) return Bit_Mapped_Set; function Union (Left : Bit_Mapped_Set; Right : Element) return Bit_Mapped_Set renames "+"; function Union (Left : Element; Right : Bit_Mapped_Set) return Bit_Mapped_Set renames "+"; These versions of "+" (Union) return Left + Make_Set ( (1 => Right) ) and Make_Set ( (1 => Left) ) + Right, respectively. function "*" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set; function Intersection (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set renames "*"; "*" (Intersection) returns the intersection of its two parameters. function "-" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set; function Difference (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set renames "-"; "-" (Difference) returns the difference of its two parameters. function "-" (Left : Bit_Mapped_Set; Right : Element) return Bit_Mapped_Set; function Difference (Left : Bit_Mapped_Set; Right : Element) return Bit_Mapped_Set renames "-"; This version of "-" (Difference) returns Left - Make_Set ( (1 => Right) ). function "/" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set; function Symmetric_Difference (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Bit_Mapped_Set renames "/"; "/" (Symmetric_Difference) returns the symmetric difference of its two parameters. function "<=" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean; function Subset (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean renames "<="; "<=" (Subset) returns True if Left is a subset of Right. Otherwise it returns False. function "<" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean; function Proper_Subset (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean renames "<"; "<" (Proper_Subset) returns True if Left is a proper subset of Right. Otherwise it returns False. function ">=" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean; function Superset (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean renames ">="; ">=" (Superset) returns True if Left is a superset of Right. Otherwise it returns False. function ">" (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean; function Proper_Superset (Left : Bit_Mapped_Set; Right : Bit_Mapped_Set) return Boolean renames ">"; ">" (Proper_Superset) returns True if Left is a proper superset of Right. Otherwise it returns False. function Member (Item : Element; Set : Bit_Mapped_Set) return Boolean; Member returns True if Item is a Member of Set. Otherwise it returns False. The type Member_List is used by Make_Set to allow an equivalent of a literal of type Bit_Mapped_Set. function Make_Set (List : Member_List) return Bit_Mapped_Set; Make_Set returns a Bit_Mapped_Set such that Member returns True for each Element in List and False for each Element not in List. function Size (Set : Bit_Mapped_Set) return Natural; Size returns the number of Elements in Set. Implementation Permission The operations defined in terms of Make_Set may be implemented directly, without calling Make_Set or any other operations of the package. Implementation Advice Bit_Mapped_Set should be implemented as a bit map; that is, as a packed array of Booleans (possibly as a component of record to allow initialization to Empty). A.X.12 Unprotected Unbounded Skip List Handling The language-defined package Ada.Containers.Skip_List_Unbounded_Unprotected provides a generic package each of whose instances yields a limited private type Unbounded_Skip_List and a set of operations. An object of a particular Skip_List_Unbounded_Unprotected Unbounded_Skip_List type represents a sorted list structure whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Containers.Skip_List_Unbounded_Unprotected has the following declaration: generic -- Ada.Containers.Skip_List_Unbounded_Unprotected type Element is private; with function "<" (Left : Element; Right : Element) return Boolean is <>; with function "=" (Left : Element; Right : Element) return Boolean is <>; package Ada.Containers.Skip_List_Unbounded_Unprotected is type Unbounded_Skip_List is limited private; procedure Clear (List : in out Unbounded_Skip_List); procedure Assign (To : out Unbounded_Skip_List; From : in Unbounded_Skip_List); type Result (Found : Boolean := False) is record case Found is when False => null; when True => Item : Element; end case; end record; function Search (List : Unbounded_Skip_List; Item : Element) return Result; procedure Insert (List : in out Unbounded_Skip_List; Item : in Element; Duplicates_Allowed : in Boolean := False); procedure Delete (List : in out Unbounded_Skip_List; Item : in Element); function Get_First (List : Unbounded_Skip_List) return Element; function Get_Last (List : Unbounded_Skip_List) return Element; function Is_Empty (List : Unbounded_Skip_List) return Boolean; function Length (List : Unbounded_Skip_List) return Natural; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (List : in out Unbounded_Skip_List; Context : in out Context_Data); private -- Ada.Containers.Skip_List_Unbounded_Unprotected -- not defined by the language end Ada.Containers.Skip_List_Unbounded_Unprotected; A skip list is a probabalistically balanced structure similar to a balanced tree in search time. Insertions and deletions are typically faster than balanced trees. Often type Element will be a record type with two parts, the "Key" and the "Value". The functions "<" and "=" will then be defined to only compare the Key parts of their parameters. An object of type Unbounded_Skip_List is initially empty. procedure Clear (List : in out Unbounded_Skip_List); Clear makes List empty. Postcondition: Is_Empty (List) Length (List) = 0 procedure Assign (To : out Unbounded_Skip_List; From : in Unbounded_Skip_List); If To and From are the same list, Assign has no effect. Otherwise, To is cleared and made into a copy of From. If, after clearing To, there is insufficient storage for copies of all the Elements in From, Storage_Error is propagated. Postcondition: Length (To) = Length (From) Type Result defines the results of function Search. function Search (List : Unbounded_Skip_List; Item : Element) return Result; If there exists a value X stored in List such that X = Item, Search returns (Found => True, Item => X) Otherwise, Search returns (Found => False) procedure Insert (List : in out Unbounded_Skip_List; Item : in Element; Duplicates_Allowed : in Boolean := False); If there is insufficient storage available to store Item in List, Storage_Error is propagated. Insert adds Item to List in the order specified by "<". If Duplicates_Allowed, Insert adds Item after any values in List which are = Item. If not Duplicates_Allowed and there is a value in List which is = Item, Insert replaces the value with Item. Postcondition: not Is_Empty (List) procedure Delete (List : in out Unbounded_Skip_List; Item : in Element); If List contains an Element X such that X = Item, Delete deletes X from List. Otherwise, Delete has no effect. If List contains more than one such Element, Delete deletes the Element that would be found by Search with Item. The user is expected to have checked for the existence of Item in List before calling Delete, using Search, Get_First, or Get_Last. function Get_First (List : Unbounded_Skip_List) return Element; If List is empty, Get_First propagates Empty_Structure_Error. Otherwise, Get_First returns the first value stored in List. function Get_Last (List : Unbounded_Skip_List) return Element; If List is empty, Get_Last propagates Empty_Structure_Error. Otherwise, Get_Last returns the last value stored in List. function Is_Empty (List : Unbounded_Skip_List) return Boolean; Is_Empty returns True if List is empty and False otherwise. function Length (List : Unbounded_Skip_List) return Natural; Length returns the number of Elements stored in List. generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (List : in out Unbounded_Skip_List; Context : in out Context_Data); An instance of this procedure applies Action to each Element stored in Over in turn in sorted order. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in Over. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a skip list while iterating over it. Possible consequences are: propagating Constraint_Error, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. Notes If duplicates are allowed during insertion, Delete may delete a different node than the one expected, with possible unexpected results. It is recommended that duplicates not be allowed if possible. A.X.13 Unprotected Unbounded Stack Handling The language-defined package Ada.Containers.Stack_Unbounded_Unprotected provides a generic package each of whose instances yields a limited private type Unbounded_Stack and a set of operations. An object of a particular Queue_Unbounded_Unprotected Unbounded_Stack type represents a stack whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Containers.Stack_Unbounded_Unprotected has the following declaration: generic -- Ada.Containers.Stack_Unbounded_Unprotected type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Containers.Stack_Unbounded_Unprotected is pragma Preelaborate; type Unbounded_Stack is limited private; procedure Clear (Stack : in out Unbounded_Stack); procedure Assign (To : out Unbounded_Stack; From : in Unbounded_Stack); procedure Push (Onto : in out Unbounded_Stack; Item : in Element); procedure Pop (From : in out Unbounded_Stack; Item : out Element); function Length (Stack : Unbounded_Stack) return Natural; function Is_Empty (Stack : Unbounded_Stack) return Boolean; function Peek (Stack : Unbounded_Stack) return Element; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Unbounded_Stack; Context : in out Context_Data); private -- Ada.Containers.Stack_Unbounded_Unprotected -- not specified by the language end Ada.Containers.Stack_Unbounded_Unprotected; A stack is an ordered collection of Elements. Elements are added to and removed from the top of a stack. A stack is a last in, first out (LIFO) data structure. An object of type Unbounded_Stack is initially empty. procedure Clear (Stack : in out Unbounded_Stack); Clear makes Stack empty. Postcondition: Is_Empty (Stack) Length (Stack) = 0 procedure Assign (To : out Unbounded_Stack; From : in Unbounded_Stack); If To and From are the same Stack, Assign has no effect. Otherwise, To is cleared and made into a copy of From. If, after clearing To, there is insufficient storage for copies of all the Elements in From, Storage_Error is propagated. Postcondition: Length (To) = Length (From) procedure Push (Onto : in out Unbounded_Stack; Item : in Element); If there is insufficient storage available to store Item in Onto, Storage_Error is propagated. Otherwise, Put adds Item to the top of Onto. Postcondition: not Is_Empty (Onto) Length (Onto) > 0 procedure Pop (From : in out Unbounded_Stack; Item : out Element); If From is empty, Empty_Structure_Error is propagated. Otherwise, the Element on the top of From is removed from From and put in Item. Precondition: not Is_Empty (From) function Length (Stack : Unbounded_Stack) return Natural; Length returns the number of Elements stored in Stack. function Is_Empty (Stack : Unbounded_Stack) return Boolean; Is_Empty returns True if Stack is empty and False otherwise. function Peek (Stack : Unbounded_Stack) return Element; If Stack is empty, Empty_Structure_Error is propagated. Otherwise, Peek returns the Element on the top of Stack without altering Stack. Precondition: not Is_Empty (Stack) generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Unbounded_Stack; Context : in out Context_Data); An instance of this procedure applies Action to each Element stored in Over in turn, from top to bottom. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the queue. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a stack while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. AARM Note This package could be implemented using an instantiation of Ada.Containers.Lists.Unbounded_Unprotected. A.X.14 Protected Bounded List Handling The language-defined package Ada.Containers.Lists.Bounded provides a generic package each of whose instances yields a protected type Bounded_List with a set of operations. An object of a particular List_Bounded_Protected Bounded_List type represents a list whose size can vary conceptually between 0 and a maximum size established at the declaration of the object. The client must also specify the ceiling priority of the protected object at its declaration. Static Semantics The library package Ada.Containers.Lists.Bounded has the following declaration: with System; generic -- Ada.Containers.Lists.Bounded type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Containers.Lists.Bounded is pragma Preelaborate; type Position is private; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Pos : in Position; Continue : out Boolean); protected type Bounded_List (Max_Size : Positive; Ceiling_Priority : System.Any_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; function First return Position; function Last return Position; function Off_List return Position; function Next (Pos : Position) return Position; function Prev (Pos : Position) return Position; procedure Insert (Item : in Element; Before : in Position; New_Pos : out Position); procedure Append (Item : in Element; New_Pos : out Position); procedure Delete (Pos : in out Position); function Get (Pos : Position) return Element; procedure Put (Pos : in Position; Item : in Element); function Is_Empty return Boolean; function Is_Full return Boolean; function Length return Natural; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Bounded_List -- not specified by the language end Bounded_List; private -- Ada.Containers.Lists.Bounded -- not specified by the language end Ada.Containers.Lists.Bounded; An object of type Bounded_List represents the same abstraction as an object of type Ada.Containers.Lists.Bounded_Unprotected.Bounded_List, but protected to allow access by multiple tasks. An object of type Bounded_List is initially empty. An object of type Position is initially invalid. procedure Clear; Clear makes the list empty. Clear effectively deletes all nodes in a list. Therefore, Position_Error is propagated if a Position, obtained before Clear is called, is used to access the list after Clear is called. Postcondition: Is_Empty function First return Position; First returns a Position indicating the first Element in the list. If the list is empty, this is the same as Off_List. function Last return Position; Last returns a Position indicating the last Element in the list. If the list is empty, this is the same as Off_List. function Off_List return Position; Off_List returns a Position that is valid for the list but indicates no Element in the list. This is the same Position returned by Next (Last) and by Prev (First). function Next (Pos : Position) return Position; If Pos is not a valid Position for the list, Position_Error is propagated. If Pos = Last, Next returns Off_List. If Pos = Off_List, Next returns First. Otherwise, Next returns a Position that indicates the next Element in the list after the Element indicated by Pos. function Prev (Pos : Position) return Position; If Pos is not a valid Position for the list, Position_Error is propagated. If Pos = First, Prev returns Off_List. If Pos = Off_List, Prev returns Last. Otherwise, Prev returns a Position that indicates the previous Element in the list before the Element indicated by Pos. procedure Insert (Item : in Element; Before : in Position; New_Pos : out Position); If the list is full, Full_Structure_Error is propagated. If Before is not a valid Position for the list, Position_Error is propagated. If the list is empty and Before /= Off_List, Position_Error is propagated. If Before = Off_List, Item is added as the last Element in the list. Otherwise, Item is inserted into the list before the Element indicated by Before. New_Pos becomes a valid Position for the list that indicates Item. Precondition: not Is_Full Postcondition: Prev (Before) = New_Pos procedure Append (Item : in Element; New_Pos : out Position); If the list is full, Full_Structure_Error is propagated. If the list is empty, Item is added as the first Element in the list. Otherwise, Item is inserted into the list after the Last element. New_Pos becomes a valid Position for the list that indicates Item. Precondition: not Is_Full Postcondition: Last = New_Pos procedure Delete (Pos : in out Position); If the list is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for the list or Pos = Off_List, Position_Error is propagated. Otherwise, the Element indicated by Pos is removed from the list. Pos is made invalid. Elements are stored in "nodes" in a list. A Position indicates an Element by indicating its node. One Position may indicate a node that has been deleted through another Position. Position_Error is propagated if such a Position is subsequently used to access the list. Precondition: not Is_Empty Postcondition: not Is_Full function Get (Pos : Position) return Element; If the list is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for the list or Pos = Off_List, Position_Error is propagated. Otherwise, Get returns the Element in the list indicated by Pos. Precondition: not Is_Empty procedure Put (Pos : in Position; Item : in Element); If the list is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for the list or Pos = Off_List, Position_Error is propagated. Otherwise, the Element in the list indicated by Pos is made to be Item. Precondition: not Is_Empty function Is_Empty return Boolean; Is_Empty returns True if the list is empty and False otherwise. function Is_Full return Boolean; Is_Full returns True if the list is full and False otherwise. function Length return Natural; Length returns the number of Elements stored in the list. procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the list and its Position in turn, from first to last. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the list. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the list. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a list while iterating over it. Possible consequences are: propagating Position_Error, propagating Constraint_Error, not processing some of the Elements stored in the list, processing some of the Elements twice, and normal processing. Implementation Permissions An implementation may reuse deleted nodes, even if that means it cannot guarantee that Position_Error will be propagated when a Position that indicates a deleted node is used to access the list. An implementation may add additional declarations to the specification of Ada.Containers.Lists.Bounded as needed to complete the private part of protected type Bounded_List. AARM Note This package could be implemented using an instantiation of Ada.Containers.Lists.Bounded_Unprotected. A.X.15 Protected Unbounded List Handling The language-defined package Ada.Containers.Lists.Unbounded provides a generic package each of whose instances yields a protected type Unbounded_List with a set of operations. An object of a particular List_Unbounded_Protected Unbounded_List type represents a list whose size can vary conceptually between 0 and a maximum size established established by available storage. Static Semantics The library package Ada.Containers.Lists.Unbounded has the following declaration: with System; generic -- Ada.Containers.Lists.Unbounded type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Containers.Lists.Unbounded is pragma Preelaborate; type Position is private; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Pos : in Position; Continue : out Boolean); protected type Unbounded_List (Ceiling_Priority : System.Any_Priority := System.Default_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; function First return Position; function Last return Position; function Off_List return Position; function Next (Pos : Position) return Position; function Prev (Pos : Position) return Position; procedure Insert (Item : in Element; Before : in Position; New_Pos : out Position); procedure Append (Item : in Element; New_Pos : out Position); procedure Delete (Pos : in out Position); function Get (Pos : Position) return Element; procedure Put (Pos : in Position; Item : in Element); function Is_Empty return Boolean; function Length return Natural; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Unbounded_List -- not specified by the language end Unbounded_List; private -- Ada.Containers.Lists.Unbounded -- not specified by the language end Ada.Containers.Lists.Unbounded; procedure Clear; Clear makes the list empty. Clear effectively deletes all nodes in a list. Therefore, Position_Error is propagated if a Position, obtained before Clear is called, is used to access the list after Clear is called. Postcondition: Is_Empty function First return Position; First returns a Position indicating the first Element in the list. If the list is empty, this is the same as Off_List. function Last return Position; Last returns a Position indicating the last Element in the list. If the list is empty, this is the same as Off_List. function Off_List return Position; Off_List returns a Position that is valid for the list but indicates no Element in the list. This is the same Position returned by Next (Last) and by Prev (First). function Next (Pos : Position) return Position; If Pos is not a valid Position for the list, Position_Error is propagated. If Pos = Last, Next returns Off_List. If Pos = Off_List, Next returns First. Otherwise, Next returns a Position that indicates the next Element in the list after the Element indicated by Pos. function Prev (Pos : Position) return Position; If Pos is not a valid Position for the list, Position_Error is propagated. If Pos = First, Prev returns Off_List. If Pos = Off_List, Prev returns Last. Otherwise, Prev returns a Position that indicates the previous Element in the list before the Element indicated by Pos. procedure Insert (Item : in Element; Before : in Position; New_Pos : out Position); If there is insufficient storage available to store Item in the list, Storage_Error is propagated. If Before is not a valid Position for the list, Position_Error is propagated. If the list is empty and Before /= Off_List, Position_Error is propagated. If Before = Off_List, Item is added as the last Element in the list. Otherwise, Item is inserted into the list before the Element indicated by Before. New_Pos becomes a valid Position for the list that indicates Item. Postcondition: Prev (Before) = New_Pos procedure Append (Item : in Element; New_Pos : out Position); If there is insufficient storage available to store Item in the list, Storage_Error is propagated. If the list is empty, Item is added as the first Element in the list. Otherwise, Item is inserted into the list after the Last element. New_Pos becomes a valid Position for the list that indicates Item. Postcondition: Last = New_Pos procedure Delete (Pos : in out Position); If the list is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for the list or Pos = Off_List, Position_Error is propagated. Otherwise, the Element indicated by Pos is removed from the list. Pos is made invalid. Elements are stored in "nodes" in a list. A Position indicates an Element by indicating its node. One Position may indicate a node that has been deleted through another Position. Position_Error is propagated if such a Position is subsequently used to access the list. Precondition: not Is_Empty function Get (Pos : Position) return Element; If the list is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for the list or Pos = Off_List, Position_Error is propagated. Otherwise, Get returns the Element in the list indicated by Pos. Precondition: not Is_Empty procedure Put (Pos : in Position; Item : in Element); If the list is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for the list or Pos = Off_List, Position_Error is propagated. Otherwise, the Element in the list indicated by Pos is made to be Item. Precondition: not Is_Empty function Is_Empty return Boolean; Is_Empty returns True if the list is empty and False otherwise. function Length return Natural; Length returns the number of Elements stored in the list. procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the list and its Position in turn, from first to last. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the list. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the list. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a list while iterating over it. Possible consequences are: propagating Position_Error, propagating Constraint_Error, not processing some of the Elements stored in the list, processing some of the Elements twice, and normal processing. Implementation Requirements No storage associated with an Unbounded_List object shall be lost upon scope exit or a call to Clear or Delete. Implementation Permissions An implementation may reuse deleted nodes, even if that means it cannot guarantee that Position_Error will be propagated when a Position that indicates a deleted node is used to access the list. An implementation may add additional declarations to the specification of Ada.Containers.Lists.Unbounded as needed to complete the private part of protected type Unbounded_List. AARM Note This package could be implemented using an instantiation of Ada.Containers.Lists.Unbounded_Unprotected. A.X.16 Protected Unbounded Bag Handling The language-defined package Ada.Containers.Bag_Unbounded provides a generic package each of whose instances yields a protected type Unbounded_Bag with a set of operations. An object of a particular Bag_Unbounded Unbounded_Bag type represents a list whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Containers.Bag_Unbounded has the following declaration: with System; generic -- Ada.Containers.Bag_Unbounded type Element is private; with function "=" (Left : Element; Right : Element) return Boolean is <>; package Ada.Containers.Bag_Unbounded is pragma Preelaborate; type Find_Result (Found : Boolean := False) is record case Found is when False => null; when True => Item : Element; end case; end record; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Continue : out Boolean); protected type Unbounded_Bag (Ceiling_Priority : System.Any_Priority := System.Default_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; procedure Add (Item : in Element); procedure Delete (Item : in Element); procedure Update (Item : in Element); function Find (Key : Element) return Find_Result; function Empty return Boolean; function Size return Natural; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Unbounded_Bag -- not specified by the language end Unbounded_Bag; end Ada.Containers.Bag_Unbounded; procedure Clear; Clear makes the bag empty. Postcondition: Is_Empty Size = 0 procedure Add (Item : in Element); If there is insufficient storage available to store Item in the bag, Storage_Error is propagated. Otherwise, Add adds Item to the bag. Postcondition: not Is_Empty procedure Delete (Item : in Element); If the bag contains an Element X such that X = Item, Delete deletes X from the bag. Otherwise, Delete has no effect. If the bag contains more than one such Element, Delete deletes one of these Elements. Which of these Elements is deleted is undefined. procedure Update (Item : in Element); If the bag contains an Element X such that X = Item, Update conceptually performs X := Item. Otherwise, Update has no effect. If the bag contains more than one such Element, Update updates one of these Elements. Which of these Elements is updated is undefined. Type Find_Result defines the results of function Find. function Find (Key : Element) return Find_Result; If the bag contains an Element X such that X = Key, Find returns (Found => True, Item => X). Otherwise, Find returns (Found => False). If the bag contains more than one such Element, Find returns one of these Elements as the Item component of the result. Which of these Elements is returned is undefined. function Empty return Boolean; Is_Empty returns True if the bag contains no elements and False otherwise. function Size return Natural; Size returns the number of elements stored in the bag. procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the bag. The order in which the Elements in the bag are processed is undefined. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the bag. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the bag. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a bag while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. Implementation Requirements No storage associated with an Unbounded_Bag object shall be lost upon scope exit or a call to Clear or Delete. Implementation Permissions An implementation may add additional declarations to the specification of Ada.Containers.Bag_Unbounded as needed to complete the private part of protected type Unbounded_Bag. AARM Note This package could be implemented using an instantiation of Ada.Containers.Bag_Unbounded_Unprotected. A.X.17 Protected Unbounded Stack Handling The language-defined package Ada.Containers.Stack_Unbounded provides a generic package each of whose instances yields a protected type Unbounded_Stack with a set of operations. An object of a particular Stack_Unbounded Unbounded_Stack type represents a stack whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Containers.Stack_Unbounded has the following declaration: with System; generic -- Ada.Containers.Stack_Unbounded type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Containers.Stack_Unbounded is pragma Preelaborate; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Continue : out Boolean); protected type Unbounded_Stack (Ceiling_Priority : System.Any_Priority := System.Default_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; procedure Push (Item : in Element); procedure Pop (Item : out Element); function Is_Empty return Boolean; function Length return Natural; function Peek return Element; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Unbounded_Stack -- not specified by the language end Unbounded_Stack; end Ada.Containers.Stack_Unbounded; procedure Clear; Clear makes the stack empty. Postcondition: Is_Empty Length = 0 procedure Push (Item : in Element); If there is insufficient storage available to store Item in the stack, Storage_Error is propagated. Otherwise, Push adds Item to the top of the stack. Postcondition: not Is_Empty procedure Pop (Item : out Element); If the stack is empty, Empty_Structure_Error is propagated. Otherwise, the Element on the top of the stack is removed from the stack and put in Item. Precondition: not Is_Empty function Is_Empty return Boolean; Is_Empty returns True if the stack contains no elements and False otherwise. function Length return Natural; Length returns the number of elements stored in the stack. function Peek return Element; If the stack is empty, Empty_Structure_Error is propagated. Otherwise, Peek returns the Element on the top of the stack without altering the stack. Precondition: not Is_Empty procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the stack in turn, from top to bottom. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the the stack. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the stack. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a stack while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in the stack, processing some of the Elements twice, and normal processing. Implementation Requirements No storage associated with an Unbounded_Stack object shall be lost upon scope exit or a call to Clear or Delete. Implementation Permissions An implementation may add additional declarations to the specification of Ada.Containers.Stack_Unbounded as needed to complete the private part of protected type Unbounded_Stack. AARM Note This package could be implemented using an instantiation of Ada.Containers.Stack_Unbounded_Unprotected. A.X.18 Assign Procedure Creation The language-defined procedure Ada.Containers.Assignment provides a generic procedure each of whose instances yields a procedure suitable for the actual parameter associated with the generic formal Assign procedure required by many of the data structure packages. Static Semantics The library procedure Ada.Containers.Assignment has the following declaration: generic -- Ada.Containers.Assignment type Element (<>) is private; procedure Ada.Containers.Assignment (To : out Element; From : in Element); pragma Pure (Ada.Containers.Assignment); An instance of Assignment performs To := From; In many cases, the desired actual Assign procedure for instantiating a data structure package is simply the predefined assignment operation. This procedure is provided as a convenience for creating such procedures. !discussion This set of packages is based on the PragmAda Reusable Components. The PragmARCs have been designed to be useful on real world problems. As such, most of them import a limited private type and an Assign procedure, unlike some libraries that put ease of use above usability by importing a private type. On the other hand, they are generally easier to use than some libraries that require a series of instantiations of generic child packages to use a single data structure. One reason for this decision is that it allows structures of structures, something that has been needed by users of the PragmARCs. A ramification of this design decision is: Many clients need to instantiate these packages with Element types for which Assign would simply be an assignment statement. Having to create an Assign procedure for such types is a repetitive activity best addressed in Ada by a generic. An instantiation of procedure PragmARC.Assignment creates the required Assign procedure. The (original) PragmARCs may be obtained from http://home.earthlink.net/~jrcarter010/pragmarc.htm and from the mirror at www.adapower.com. The files of interest are pragmarc.ads -- The parent package contains some common exceptions pragmarc-bag*.ads pragmarc-list*.ads pragmarc-queue*.ads pragmarc-set_discrete.ads pragmarc-skip_list_unbounded.ads pragmarc-stack*.ads In addition, there are 2 "helper" packages pragmarc-assignment.ads A.X.1 Package Ada.Containers Package Ada.Containers contains the declarations of exceptions common to many of the data-structures packages. A.X.2 Unprotected Bounded-Length List Handling Protected lists may not be a very high priority, but one very useful component for concurrent programs is a protected queue. This proposal does present concurrent queue components, so the distinction is preserved for all structures. Given a type such as Bounded_List, it is clear that the use of controlled types does not allow assignment between objects with different maximum sizes, even when the logical value to be assigned will fit in the destination object. This requires that assignment be performed by a procedure. To allow creating structures of structures, this dictates the form of the generic formal part. Procedure Assign needs to be able to check that its parameters are not the same list; otherwise, the result could be an empty list, rather than the original list. In the reference implementation, this is done by comparing list IDs. Since this involves reading the value of To, it would be more accurate for To to have mode "in out". However, the imported Assign procedure has To of mode "out", rather than "in out", to allow scalars to be used without problems with passing an uninitialized variable to To and having it fail constraint checks. To allow structures of structures, Assign for Bounded_List needs To to be mode "out" as well. The reference implementation uses the rules for parameter passing for mode "out" to insure that the ID can be read. The intended behavior of Position checking is: Each list has a unique ID. The ID type includes a value that is invalid. Each Position contains the ID of the list it references; an uninitialized Position contains the invalid ID. This implies that each list has a unique Off_List Position. Each node in the list contains the list's ID. A node which is not part of the logical value of a list contains the invalid ID and is considered an invalid node. Delete invalidates both the node and the position used to delete it. All operations involving Positions check that the ID in the Position matches the ID of the list involved. When a node is also involved, the operation also checks the ID of the node. If a check fails, Position_Error is propagated. This is what's intended by saying that Position_Error is propagated when a Position that indicates a deleted node is used. These checks detect and prevent the most common user errors when using lists, without the overhead in time and space required to detect all possible errors. This seems like a reasonable compromise between completely safe and complex at one extreme and unsafe and simple at the other. It is probably a good idea to protect the source of list IDs so that multiple tasks may elaborate objects of type Bounded_List at the same time. The reference implementation does this. The reference implementation initializes a free list of unused nodes. Bounded_List is a controlled type to achieve this. The reference implementation of Ada.Containers.Lists.Bounded_Unprotected is PragmARC.List_Bounded_Unprotected; it differs only in its name, the names of the exceptions, and the name of the type Bounded_List. A.X.3 Unprotected Unbounded-Length List Handling Unbounded lists have the same interface as bounded lists, except for the absence of Is_Full and the propagating of Storage_Error rather than Full_Structure_Error. The intended behavior of Position checking is the same as for bounded lists. In the reference implementation, there is an explicit off-list node for each list (without space for an Element). The access value designating this node is used as the list ID. The reference implementation of Ada.Containers.Lists.Unbounded_Unprotected is PragmARC.List_Unbounded_Unprotected; it differs in its name, the names of the exceptions, and the name of type Unbounded_List. It also has a Sort procedure not specified here. Type Unbounded_List is a controlled type in the reference implementation. The example below is identical to the bounded list example, except for the name of the generic package and the absence of a discriminant on Unbounded_List. A.X.4 Unprotected Unbounded Bag Handling The reference implementation of Ada.Containers.Bag_Unbounded_Unprotected is PragmARC.Bag_Unbounded_Unprotected; it differs in its name, the name of type Unbounded_Bag, and uses Empty rather than Is_Empty. Type Unbounded_Bag is a wrapper around a List_Unbounded_Unprotected Unbounded_List in the reference Implementation. A.X.5 Unprotected Bounded Queue Handling The reference implementation of Ada.Containers.Queue_Bounded_Unprotected is PragmARC.Queue_Bounded_Unprotected; it differs in its name, the name of type Bounded_Queue, and the names of exceptions. Type Bounded_Queue is a wrapper around a List_Bounded_Unprotected Bounded_List in the reference implementation. A.X.6 Protected Bounded Queue Handling Protected queues are a common and useful method for asynchronous communications among tasks; therefore, I will present protected versions of the queue components. Protected versions of other components are also possible, but less essential, so they will only be presented if time and resources allow. The naming scheme used here is based on that of the reference implementation. The overhead of using a protected object in a sequential program is small; if the implementation uses the ceiling priority locking scheme, the overhead is zero. It is therefore likely that protected components will be used in both concurrent and sequential systems, simply to reduce the complexity of switching between the two forms of a component. In addition, many people like the Name.Operation syntax of a protected object, as well as the fact that without a use clause one does not need to repeat the instantiation name to use the operations. For these reasons, the shorter name is reserved for protected components, with the longer named used for the unprotected versions. I'm not sure if the Implementation Permission is needed, or if it's already implicit. It seems a failing of the language that a visible declaration is needed for what should be completely private information (that is, the type used in the private part of Bounded_Queue). This proposal is based on the PragmAda Reusable Components, which are Ada 95. If the proposed subprogram parameters (or equivalent) are adopted in the revision of the language, such a parameter should be used for the Action parameter of Iterate. In that case, it is possible to eliminate the context-data parameters from all iterators. Those parameters exist only to allow supplying local context data to protected iterator procedures. The reference version of Ada.Containers.Queue_Bounded is PragmARC.Queue_Bounded. It differs in its name and the names of exceptions. It contains a visible instantiation of PragmARC.Queue_Bounded_Unprotected named Implementation; the private part of Bounded_Queue is Queue : Implementation.Bounded_Queue; A.X.7 Protected Blocking Bounded Queue Handling This package is identical to Ada.Containers.Queue_Bounded except that Put and Get are entries. Blocking queues are often useful in concurrent systems. The reference version of Ada.Containers.Queue_Bounded_Blocking is PragmARC.Queue_Bounded_Blocking. It differs in its name, the name of type Bounded_Queue, and the names of exceptions. It contains a visible instantiation of PragmARC.Queue_Bounded_Unprotected named Implementation; the private part of Bounded_Queue is Queue : Implementation.Bounded_Queue; A.X.8 Unprotected Unbounded Queue Handling The reference implementation of Ada.Containers.Queue_Unbounded_Unprotected is PragmARC.Queue_Unbounded_Unprotected; it differs in its name, the name of type Unbounded_Queue, and the names of exceptions. Type Unbounded_Queue is a wrapper around a List_Unbounded_Unprotected Unbounded_List in the reference implementation. A.X.9 Protected Unbounded Queue Handling The reference version of Ada.Containers.Queue_Unbounded is PragmARC.Queue_Unbounded. It differs in its name, the name of type Unbounded_Queue, and the names of exceptions. It contains a visible instantiation of PragmARC.Queue_Unbounded_Unprotected named Implementation; the private part of Unbounded_Queue is Queue : Implementation.Unbounded_Queue; A.X.10 Protected Blocking Unbounded Queue Handling The reference version of Ada.Containers.Queue_Unbounded_Blocking is PragmARC.Queue_Unbounded_Blocking. It differs in its name, the name of type Unbounded_Queue, and the names of exceptions. It contains a visible instantiation of PragmARC.Queue_Unbounded_Unprotected named Implementation; the private part of Unbounded_Queue is Queue : Implementation.Unbounded_Queue; A.X.11 Discrete Set Handling We would really like to use "in" instead of "Member". The reference implementation of Ada.Containers.Set_Bit_Mapped is PragmARC.Set_Discrete. It differs in some of the type and parameter names used. In the reference implementation, Bit_Mapped_Set is a record containing an array component. The array type has Element as its index and Boolean components, and has 'Component_Size defined as one and pragma Pack applied to it. A.X.12 Unprotected Unbounded Skip List Handling The Action procedure supplied to Iterate is not allowed to modify the Element passed to it, since that could result in the list becoming unbalanced. This differs from the iteration procedures in the other data structures packages. The reference implementation of Ada.Containers.Skip_List_Unbounded_Unprotected is PragmARC.Skip_List_Unbounded; it differs in its name and the names of types and exceptions. Type Unbounded_Skip_List is a controlled type in the reference implementation. A.X.13 Unprotected Unbounded Stack Handling The reference implementation of Ada.Containers.Stack_Unbounded_Unprotected is PragmARC.Stack_Unbounded_Unprotected; it differs in its name, the name of type Unbounded_Stack, and the names of exceptions. Type Unbounded_Stack is a wrapper around a List_Unbounded_Unprotected Unbounded_List in the reference implementation. A.X.14 Protected Bounded List Handling The reference version of Ada.Containers.Lists.Bounded is PragmARC.List_Bounded. It differs in its name, the names of exceptions, and the name of the type Bounded_List. It contains a visible instantiation of PragmARC.List_Bounded_Unprotected named Implementation; the private part of Bounded_List is List : Implementation.Bounded_List; Type Position is implemented as a type derived from Implementation.Position. A.X.15 Protected Unbounded List Handling The reference version of Ada.Containers.Lists.Unbounded is PragmARC.List_Unbounded. It differs in its name, the names of exceptions, the name of type Unbounded_List, and contains a sort operation not provided here. It contains a visible instantiation of PragmARC.List_Unbounded_Unprotected named Implementation; the private part of Unbounded_List is List : Implementation.Unbounded_List; Type Position is implemented as a type derived from Implementation.Position. A.X.16 Protected Unbounded Bag Handling The reference version of Ada.Containers.Bag_Unbounded is PragmARC.Bag_Unbounded. It differs in its name, the name of the type Unbounded_Bag, and the names of exceptions. It contains a visible instantiation of PragmARC.Bag_Unbounded_Unprotected named Implementation; the private part of Unbounded_Bag is Bag : Implementation.Unbounded_Bag; A.X.17 Protected Unbounded Stack Handling The reference version of Ada.Containers.Stack_Unbounded is PragmARC.Stack_Unbounded. It differs in its name, the name of type Unbounded_Stack, and the names of exceptions. It contains a visible instantiation of PragmARC.Stack_Unbounded_Unprotected named Implementation; the private part of Unbounded_Stack is Stack : Implementation.Unbounded_Stack; !example A.X.2 Unprotected Bounded-Length List Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 1,000 or fewer integers, one per line, read all of the values in Input and store them in a list in the order read. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_List is new Ada.Containers.Lists.Bounded_Unprotected (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Pos : in Integer_List.Position; Continue : out Boolean) is begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_List.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; List : Integer_List.Bounded_List (Max_Size => 1_000); Pos : Integer_List.Position; Off_List : constant Integer_List.Position := Integer_List.Off_List (List); use Integer_List; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Insert (Into => List, Before => Off_List, Item => I, New_Pos => Pos); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Length (List) ) ); Put_All (Over => List, Context => Dummy); end; A.X.3 Unprotected Unbounded-Length List Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 1,000 or fewer integers, one per line, read all of the values in Input and store them in a list in the order read. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_List is new Ada.Containers.Lists.Unbounded_Unprotected (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Pos : in Integer_List.Position; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_List.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; List : Integer_List.Unbounded_List; Pos : Integer_List.Position; Off_List : constant Integer_List.Position := Integer_List.Off_List (List); use Integer_List; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Insert (Into => List, Before => Off_List, Item => I, New_Pos => Pos); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Length (List) ) ); Put_All (Over => List, Context => Dummy); end; A.X.4 Unprotected Unbounded Bag Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 0 or more integers, one per line, read all of the values in Input and store them in a bag. Once the entire file has been read, output the number of values read followed by all the values to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Bag is new Ada.Containers.Bag_Unbounded_Unprotected (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_Bag.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; Bag : Integer_Bag.Unbounded_Bag; use Integer_Bag; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Add (Item => I, Into => Bag); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Size (Bag) ) ); Put_All (Over => Bag, Context => Dummy); end; A.X.5 Unprotected Bounded Queue Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 1,000 or fewer integers, one per line, read all of the values in Input and store them in a bag. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Queue is new Ada.Containers.Queue_Bounded_Unprotected (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_Queue.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; Queue : Integer_Queue.Bounded_Queue (Max_Size => 1000); use Integer_Queue; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Put (Item => I, Into => Queue); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Length (Queue) ) ); Put_All (Over => Queue, Context => Dummy); end; A.X.6 Protected Bounded Queue Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 1,000 or fewer integers, one per line, read all of the values in Input and store them in a queue. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Queue is new Ada.Containers.Queue_Bounded (Element => Integer); procedure Put (I : in out Integer; Context : in out Integer_Queue.Context_Data'Class; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Integer_Queue.Context_Data; Queue : Integer_Queue.Bounded_Queue (Max_Size => 1000, Ceiling_Priority => System.Default_Priority); begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Queue.Put (Item => I); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Queue.Length) ); Queue.Iterate (Action => Put'access, Context => Dummy); end; A.X.7 Protected Blocking Bounded Queue Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 1,000 or fewer integers, one per line, read all of the values in Input and store them in a queue. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Queue is new Ada.Containers.Queue_Bounded_Blocking (Element => Integer); procedure Put (I : in out Integer; Context : in out Integer_Queue.Context_Data'Class; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Integer_Queue.Context_Data; Queue : Integer_Queue.Bounded_Queue (Max_Size => 1000, Ceiling_Priority => System.Default_Priority); begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Queue.Put (Item => I); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Queue.Length) ); Queue.Iterate (Action => Put'access, Context => Dummy); end; A.X.8 Unprotected Unbounded Queue Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a queue. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Queue is new Ada.Containers.Queue_Unbounded_Unprotected (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_Queue.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; Queue : Integer_Queue.Unbounded_Queue; use Integer_Queue; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Put (Item => I, Into => Queue); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Length (Queue) ) ); Put_All (Over => Queue, Context => Dummy); end; A.X.9 Protected Unbounded Queue Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a queue. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Queue is new Ada.Containers.Queue_Unbounded (Element => Integer); procedure Put (I : in out Integer; Context : in out Integer_Queue.Context_Data'Class; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Integer_Queue.Context_Data; Queue : Integer_Queue.Unbounded_Queue; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Queue.Put (Item => I); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Queue.Length) ); Queue.Iterate (Action => Put'access, Context => Dummy); end; A.X.10 Protected Blocking Unbounded Queue Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a queue. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Queue is new Ada.Containers.Queue_Unbounded_Blocking (Element => Integer); procedure Put (I : in out Integer; Context : in out Integer_Queue.Context_Data'Class; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Integer_Queue.Context_Data; Queue : Integer_Queue.Unbounded_Queue; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Queue.Put (Item => I); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Queue.Length) ); Queue.Iterate (Action => Put'access, Context => Dummy); end; A.X.11 Discrete Set Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers in the range 1 .. 1_000, one per line, read all of the values in Input and add them to a set. Once the entire file has been read, output the number of unique values read followed by all the unique values, in ascending order, to the standard output. declare subtype Number is Integer range 1 .. 1_000; package Number_Set is new Ada.Containers.Set_Bit_Mapped (Element => Number); I : Number; Set : Number_Set.Bit_Mapped_Set; use type Number_Set.Bit_Mapped_Set; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Set := Set + I; end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Number_Set.Size (Set) ) ); Print_All : for J in Number loop if Number_Set.Member (J, Set) then Ada.Text_IO.Put_Line (Item => Integer'Image (J) ); end if; end loop Print_All; end; A.X.12 Unprotected Unbounded Skip List Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a skip list. Once the entire file has been read, output the number of values read followed by all the unique values, in ascending order, to the standard output. declare package Integer_List is new Ada.Containers.Skip_List_Unbounded_Unprotected (Element => Integer); procedure Put (I : in Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_List.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; List : Integer_List.Unbounded_Skip_List; use Integer_List; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Insert (Item => I, List => List); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Length (List) ) ); Put_All (Over => List, Context => Dummy); end; A.X.13 Unprotected Unbounded Stack Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a stack. Once the entire file has been read, output the number of values read followed by all the values, in reverse order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Stack is new Ada.Containers.Stack_Unbounded_Unprotected (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_Stack.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; Stack : Integer_Stack.Unbounded_Stack; use Integer_Stack; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Push (Item => I, Onto => Queue); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Length (Stack) ) ); Put_All (Over => Stack, Context => Dummy); end; A.X.14 Protected Bounded List Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 1,000 or fewer integers, one per line, read all of the values in Input and store them in a list in the order read. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_List is new Ada.Containers.Lists.Bounded (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Pos : in Integer_List.Position; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Boolean := False; List : Integer_List.Bounded_List (Max_Size => 1_000, Ceiling_Priority => System.Default_Priority); Pos : Integer_List.Position; Off_List : constant Integer_List.Position := List.Off_List; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); List.Insert (Before => Off_List, Item => I, New_Pos => Pos); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (List.Length) ); List.Iterate (Action => Put'access, Context => Dummy); end; A.X.15 Protected Unbounded List Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a list in the order read. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_List is new Ada.Containers.Lists.Unbounded (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Pos : in Integer_List.Position; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Boolean := False; List : Integer_List.Unbounded_List; Pos : Integer_List.Position; Off_List : constant Integer_List.Position := List.Off_List; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); List.Insert (Before => Off_List, Item => I, New_Pos => Pos); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (List.Length) ); List.Iterate (Action => Put'access, Context => Dummy); end; A.X.16 Protected Unbounded Bag Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a bag. Once the entire file has been read, output the number of values read followed by all the values to the standard output. declare package Integer_Bag is new Ada.Containers.Bag_Unbounded (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Boolean := False; Bag : Integer_Bag.Unbounded_Bag; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Bag.Add (Item => I); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Bag.Size) ); Bag.Iterate (Action => Put'access, Context => Dummy); end; A.X.17 Protected Unbounded Stack Handling Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a stack. Once the entire file has been read, output the number of values read followed by all the values, in reverse order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Stack is new Ada.Containers.Stack_Unbounded (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Boolean := False; Stack : Integer_Stack.Unbounded_Stack; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Stack.Push (Item => I); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Stack.Length) ); Stack.Iterate (Action => Put'access, Context => Dummy); end; A.X.18 Assign Procedure Creation Instantiate Stack_Unbounded for the predefined type Float. procedure Assign is new Ada.Containers.Assignment (Element => Float); package Float_Stack is new Ada.Containers.Stack_Unbounded (Element => Float); !ACATS Test ACATS test(s) need to be created. !appendix From: Jeffrey Carter Sent: Monday, June 10, 2002 9:15 PM I would like to propose the data structure components of the PragmAda Reusable Components as the starting point for standard data structure components for Ada. This message is not a formal proposal to the ARG, but the first step in generating something that will be in an acceptable format for a proposal. The PragmARCs seem to fit the proposed class of potential reusable components well. The utility of basic data structures is well established. The PragmARCs have existed for some time, have a reference implementation, and have some evidence of being usable from their use on a number of projects by a number of users. They are the basis for the data structure components of M. Erdmann's ASCL project. Unfortunately, Erdmann is not interested in submitting the ASCL to the ARG, so I will make this proposal (if it gets that far). The PragmARC data structures are completely portable Ada 95, and a number of test programs exist and are distributed with the PragmARCs. I'll try to lay out some indication of what might go into the standard sections of a proposal: !summary Data structure are basic to the field of software engineering. Having a standard set of data structure components will be good for the language and its users. It is proposed that the data structure components of the PragmAda Reusable Components be adopted as the basis for a standard data structure library. These components have been designed to be as generally useful as possible; within that constraint they are also intended to be as easy to use a possible. !problem Data structures are basic to the field of software engineering; so basic that one of Wirth's books is titled @i. Ada has no standard data structure library (except for Ada.Strings), unlike many languages. Having a standard set of data structure components defined for the language will help promote the use of Ada, increase developer efficiency, and promote portability. !proposal The data structure components of the PragmAda Reusable Components should be adopted as the basis for a standard data structure library. !wording [I will not present the package specifications here yet. I know they will have to be included at some point in this process, presumably after some modification, so it seems prudent to wait until those modifications are known and made. The PragmARCs may be obtained from http://home.earthlink.net/~jrcarter010/pragmarc.htm and from the mirror at www.adapower.com. The files of interest are pragmarc.ads -- The parent package contains some common exceptions pragmarc-bag*.ads pragmarc-list*.ads pragmarc-queue*.ads pragmarc-set_discrete.ads pragmarc-skip_list_unbounded.ads pragmarc-stack*.ads In addition, there are 2 "helper" packages pragmarc-assignment.ads pragmarc-scalar_wrapping.ads See the "discussion" section for an explanation of the "helper" packages. The package specifications contain a semi-formal description of the semantics of each operation as comments associated with the operations, rather than separately as in the ARM. These should serve as a good starting point for creating the desired ARM wording.] !example [This section does not seem relevant for such a proposal, given the very general nature of the problem. Providing examples of the use of each data structure would be very lengthy, and would not seem to add much to the proposal.] ! discussion The PragmARCs have been designed to be useful on real world problems. As such, most of them import a limited private type and an Assign procedure, unlike some libraries that put ease of use above usability by importing a private type. On the other hand, they are generally easier to use than some libraries that require a series of instantiations of generic child packages to use a single data structure. One reason for this decision is that it allows structures of structures, something that has been needed by users of the PragmARCs. Some ramifications of this design decision are: Many clients need to instantiate these packages with Element types for which Assign would simply be an assignment statement. Having to create an Assign procedure for such types is a repetitive activity best addressed in Ada by a generic. An instantiation of procedure PragmARC.Assignment creates the required Assign procedure. ARM 6.4.1 seems to imply that a check may be made of the value of a scalar parameter of mode "in out" before it is copied in. Since the data structures will declare uninitialized variables of the Element type and pass them as the To parameter of Assign, this check could fail if Element is a scalar subtype. To avoid this requires that the client create a record type with a single component of the scalar subtype, and create an Assign procedure for this record type. Again, this is repetitive. An instantiation of PragmARC.Scalar_Wrapping creates the record type and the Assign procedure. It would be nice if my interpretation of the ARM is incorrect, and this extra step is unnecessary. Luckily, structures of scalars seem to be rare in real world projects. [end of draft sections] One obvious question is what name these components should have if they are adopted. It does not seem right for a standard library to be named for PragmAda Software Engineering. Ideally, most of the PragmARCs (or equivalent packages) would be adopted as standard components, and PragmAda could cease to exist as a supplier of reusable components. Remember, this is not a proposal, but the first step in creating a proposal. The above is intended serve as a framework for a discussion of how it needs to be modified to become a proposal that may be taken seriously by the ARG. Thanks for your time and assistance. **************************************************************** From: Jeffrey Carter Sent: Thursday, September 19, 2002 3:07 PM With the blessings of Randy Brukardt, I will be making incremental updates to AI-302 until it becomes a complete proposal in the format expected by the ARG. This is the first such update. I will use the name Something for the parent package since, on Randy's advice, I have renamed the exceptions to reflect the convention of ending in _Error. The package is no longer exactly the same as the parent package of the PragmAda Reusable Components, so I will not use the name PragmARC. In any case, it seems it should be up to the ARG to decide the name of the package, whether as part of Ada or as part of a new standard package hierarchy. !wording A.X Data Structures This clause presents the specifications of the package Something and several child packages, which provide abstract data types representing basic, common data structures. Lists, queues, stacks, bags, and skip lists are supported. A.X.1 The Package Something The package Something provides declarations common to the data-structures packages. Static Semantics The library package Something has the following declaration: package Something is pragma Pure; Empty_Structure_Error : exception; Full_Structure_Error : exception; Storage_Exhaustion_Error : exception; Too_Short_Error : exception; end Something; The exception Empty_Structure_Error is raised by operations when an attempt is made to access data in an empty structure. The exception Full_Structure_Error is raised by operations on bounded structures when an attempt is made to add data to a full structure. The exception Storage_Exhaustion_Error is raised by operations on unbounded structures when there is not enough storage available for an operation. The exception Too_Short_Error is raised by the Assign operation for bounded structures if the destination structure does not have a large enough maximum size to hold the data in the source structure. !discussion Package Something contains the declarations of exceptions common to many of the data-structures packages. This package is based on the PragmAda Reusable Components, which declare an exception named Storage_Exhausted. This exception is used rather than propagating Storage_Error primarily to allow a client to easily distinguish between memory problems with the PragmARCs (fairly rare) and memory problems with the client's code (more likely). Storage_Exhaustion_Error is the equivalent exception in package Something. It may be desirable to eliminate this distinction for standard components and simply propagate Storage_Error. **************************************************************** From: Jeffrey Carter Sent: Monday, September 30, 2002 11:44 PM !wording A.X.2 Unprotected Bounded-Length List Handling The language-defined package Something.List_Bounded_Unprotected provides a generic package each of whose instances yields a limited private type Handle, a private type Position, and a set of operations. An object of a particular List_Bounded_Unprotected Handle type represents a list whose length can vary conceptually between 0 and a maximum size established at the declaration of the object. Object of type Position are used to indicate values of type Element stored in a list. Static Semantics The library package Something.List_Bounded_Unprotected has the following declaration: generic -- Something.List_Bounded_Unprotected type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Something.List_Bounded_Unprotected is pragma Preelaborate; type Handle (Max_Size : Positive) is limited private; type Position is private; Position_Error : exception; procedure Assign (To : out Handle; From : in Handle); procedure Clear (List : in out Handle); -- Operations to obtain valid positions for lists: function First (List : Handle) return Position; function Last (List : Handle) return Position; function Off_List (List : Handle) return Position; -- Operations to obtain valid positions from valid positions: function Next (Pos : Position; List : Handle) return Position; function Prev (Pos : Position; List : Handle) return Position; -- Operations to manipulate lists procedure Insert (Into : in out Handle; Item : in Element; Before : in Position; New_Pos : out Position); procedure Append (Into : in out Handle; Item : in Element; After : in Position; New_Pos : out Position); procedure Delete (From : in out Handle; Pos : in out Position); function Get (From : Handle; Pos : Position) return Element; procedure Put (Into : in out Handle; Pos : in Position; Item : in Element); function Is_Empty (List : Handle) return Boolean; function Is_Full (List : Handle) return Boolean; function Length (List : Handle) return Natural; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Pos : in Position; Continue : out Boolean); procedure Iterate (Over : in out Handle; Context : in out Context_Data); private -- Something.List_Bounded_Unprotected -- not specified by the language end Something.List_Bounded_Unprotected; A list is a sequence of Elements. Each Element in the sequence may be accessed through a Position; we say that the Position indicates the Element. Elements may be added to, deleted from, accessed, or modified in a list at any position. Using First and Next, a list may be traversed from the first Element to the last; using Last and Prev, from the last Element to the first. An object of type Handle is initially empty. An object of type Position is initially invalid. Handle's discriminant, Max_Size, is the maximum number of Elements that may be stored in a list. procedure Assign (To : out Handle; From : in Handle); If To and From are the same list, Assign has no effect. If To.Max_Size <= Length (From), Something.Too_Short_Error is propagated. Otherwise, To is cleared and made into a copy of From. Precondition: To.Max_Size <= Length (From) Postcondition: Length (To) = Length (From) procedure Clear (List : in out Handle); This procedure makes List empty. Postcondition: Is_Empty (List) Length (List) = 0 function First (List : Handle) return Position; This function returns a Position indicating the first Element in List. If List is empty, this is the same as Off_List (List). function Last (List : Handle) return Position; This function returns a Position indicating the last Element in List. If List is empty, this is the same as Off_List (List). function Off_List (List : Handle) return Position; This function returns a Position that is valid for List but indicates no Element in List. This is the same Position returned by Next (Last (List), List) and by Prev (First (List), List). function Next (Pos : Position; List : Handle) return Position; If Pos is not a valid Position for List, Position_Error is propagated. If Pos = Last (List), this function returns Off_List (List). Otherwise, this function returns a Position that indicates the next Element in List after the Element indicated by Pos. function Prev (Pos : Position; List : Handle) return Position; If Pos is not a valid Position for List, Position_Error is propagated. If Pos = First (List), this function returns Off_List (List). Otherwise, this function returns a Position that indicates the previous Element in List before the Element indicated by Pos. procedure Insert (Into : in out Handle; Item : in Element; Before : in Position; New_Pos : out Position); If Into is full, Something.Full_Structure_Error is propagated. If Before is not a valid Position for Into, Position_Error is propagated. If Into is empty and Before /= Off_List (Into), Position_Error is propagated. If Before = Off_List (List), Item is added as the last Element in Into. Otherwise, Item is inserted into Into before the Element indicated by Before. New_Pos becomes a valid Position for Into that indicates Item. Precondition: not Is_Full (Into) Postcondition: Prev (Before, Into) = New_Pos procedure Append (Into : in out Handle; Item : in Element; After : in Position; New_Pos : out Position); If Into is full, Something.Full_Structure_Error is propagated. If After is not a valid Position for Into, Position_Error is propagated. If Into is empty and After /= Off_List (Into), Position_Error is propagated. If After = Off_List (List), Item is added as the first Element in Into. Otherwise, Item is inserted into Into after the Element indicated by After. New_Pos becomes a valid Position for Into that indicates Item. Precondition: not Is_Full (Into) Postcondition: Next (After, Into) = New_Pos procedure Delete (From : in out Handle; Pos : in out Position); If From is empty, Something.Empty_Structure_Error is propagated. If Pos is not a valid Position for From or Pos = Off_List (From), Position_Error is propagated. Otherwise, the Element indicated by Pos is removed from From. Pos is made invalid. Elements are stored in "nodes" in a list. A Position indicates an Element by indicating its node. One Position may indicate a node that has been deleted through another Position. Position_Error is propagated if such a Position is subsequently used to access the list. Clear effectively deletes all nodes in a list. Therefore, Position_Error is propagated if a Position, obtained before Clear is called, is used to access the list after Clear is called. Precondition: not Is_Empty (From) Postcondition: not Is_Full (From) function Get (From : Handle; Pos : Position) return Element; If From is empty, Something.Empty_Structure_Error is propagated. If Pos is not a valid Position for From or Pos = Off_List (From), Position_Error is propagated. Otherwise, this function returns the Element in From indicated by Pos. Precondition: not Is_Empty (From) procedure Put (Into : in out Handle; Pos : in Position; Item : in Element); If Into is empty, Something.Empty_Structure_Error is propagated. If Pos is not a valid Position for Into or Pos = Off_List (Into), Position_Error is propagated. Otherwise, the Element in Into indicated by Pos is made to be Item. Precondition: not Is_Empty (Into) function Is_Empty (List : Handle) return Boolean; This function returns True if List is empty and False otherwise. function Is_Full (List : Handle) return Boolean; This function returns True if List is full and False otherwise. function Length (List : Handle) return Natural; This function returns the number of Elements stored in List. generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Pos : in Position; Continue : out Boolean); procedure Iterate (Over : in out Handle; Context : in out Context_Data); An instance of this procedure applies Action to each Element stored in Over and its Position in turn, from first to last. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a list while iterating over it. Possible consequences are: propagating Position_Error, propagating Constraint_Error, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. Implementation Permission An implementation may reuse deleted nodes, even if that means it cannot guarantee that Position_Error will be propagated when a Position that indicates a deleted node is used to access the list. !discussion Protected lists may not be a very high priority, but one very useful component for concurrent programs is a protected queue. This proposal does present concurrent queue components, so the distinction is preserved for all structures. Given a type such as Handle, it is clear that the use of controlled types does not allow assignment between objects with different maximum sizes, even when the logical value to be assigned will fit in the destination object. This requires that assignment be performed by a procedure. To allow creating structures of structures, this dictates the form of the generic formal part. Procedure Assign needs to be able to check that its parameters are not the same list; otherwise, the result could be an empty list, rather than the original list. In the reference implementation, this is done by comparing list IDs. Since this involves reading the value of To, it would be more accurate for To to have mode "in out". However, the imported Assign procedure has To of mode "out", rather than "in out", to allow scalars to be used without problems with passing an uninitialized variable to To and having it fail constraint checks. To allow structures of structures, Assign for Handle needs To to be mode "out" as well. The reference implementation uses the rules for parameter passing for mode "out" to insure that the ID can be read. The intended behavior of Position checking is: Each list has a unique ID. The ID type includes a value that is invalid. Each Position contains the ID of the list it references; an uninitialized Position contains the invalid ID. This implies that each list has a unique Off_List Position. Each node in the list contains the list's ID. A node which is not part of the logical value of a list contains the invalid ID and is considered an invalid node. Delete invalidates both the node and the position used to delete it. All operations involving Positions check that the ID in the Position matches the ID of the list involved. When a node is also involved, the operation also checks the ID of the node. If a check fails, Position_Error is propagated. This is what's intended by saying that Position_Error is propagated when a Position that indicates a deleted node is used. These checks detect and prevent the most common user errors when using lists, without the overhead in time and space required to detect all possible errors. This seems like a reasonable compromise between completely safe and complex at one extreme and unsafe and simple at the other. It is probably a good idea to protect the source of list IDs so that multiple tasks may elaborate objects of type Handle at the same time. The reference implementation does this. The reference implementation initializes a free list of unused nodes. Handle is a controlled type to achieve this. The reference implementation of Something.List_Bounded_Unprotected is PragmARC.List_Bounded_Unprotected; it differs only in its name and the names of the exceptions. !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 1,000 or fewer integers, one per line, read all of the values in Input and store them in a list in the order read. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_List is new Something.List_Bounded_Unprotected (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Pos : in Integer_List.Position; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_List.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; List : Integer_List.Handle (Max_Size => 1_000); Pos : Integer_List.Position; Off_List : constant Integer_List.Position := Integer_List.Off_List (List); use Integer_List; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Insert (Into => List, Before => Off_List, Item => I, New_Pos => Pos); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Length (List) ) ); Put_All (Over => List, Context => Dummy); end; **************************************************************** From: Christoph Grein Sent: Tuesday, October 1, 2002 1:35 AM > function Next (Pos : Position; List : Handle) return Position; > > If Pos is not a valid Position for List, Position_Error is propagated. > > If Pos = Last (List), this function returns Off_List (List). What if Pos = Off_List? Off_List is a valid value. Probably it should return Off_List. > function Prev (Pos : Position; List : Handle) return Position; Like for Next. > procedure Insert (Into : in out Handle; > Item : in Element; > Before : in Position; > New_Pos : out Position); > procedure Append (Into : in out Handle; > Item : in Element; > After : in Position; > New_Pos : out Position); The discription is contrary to the (or my?) intuitive feeling that Insert at Off_List inserts before the start, Append at Off_List appends after the end. Insert should perhaps be called Prepend. > Clear effectively deletes all nodes in a list. Therefore, Position_Error > is propagated if a Position, obtained before Clear is called, is used to > access the list after Clear is called. > > Precondition: not Is_Empty (From) > Postcondition: not Is_Full (From) Is this a precondition for Clear? Clear on empty list just should do nothing. **************************************************************** From: Christoph Grein Sent: Tuesday, October 1, 2002 1:35 AM To elaborate a bit further the contra-intuitive meaning: If you want to fill a list in sequential order, the design seems to be to use loop get (Item); Append (List, Item, After => Last (List), New_Pos => ...); end loop; If you however do loop Get (Item); Append (List, Item, After => Off_List (List), New_Pos => ...); end list; which seems most obvious, you get, contrary to intuition, the list in reverse order! :-( Yuck! **************************************************************** From: Jeffrey Carter Sent: Tuesday, October 1, 2002 1:38 PM >> function Next (Pos : Position; List : Handle) return Position; >> >>If Pos is not a valid Position for List, Position_Error is propagated. >> >>If Pos = Last (List), this function returns Off_List (List). > > > What if Pos = Off_List? Off_List is a valid value. Probably it should return > Off_List. > > >> function Prev (Pos : Position; List : Handle) return Position; > > > Like for Next. Good points. The idea is that Next (Last) = Off_List, Next (Off_List) = First, Prev (First) = Off_List, & Prev (Off_List) = Last. Please add to the wording for Next: If Pos = Off_List (List), this function returns First (List). Add to the wording for Prev: If Pos = Off_List (List), this function returns Last (List). >> procedure Insert (Into : in out Handle; >> Item : in Element; >> Before : in Position; >> New_Pos : out Position); >> procedure Append (Into : in out Handle; >> Item : in Element; >> After : in Position; >> New_Pos : out Position); > > > The discription is contrary to the (or my?) intuitive feeling that Insert at > Off_List inserts before the start, Append at Off_List appends after the end. I hope the additional rules given above clarify this. If you like, you may consider the ends of the list curving towards each other, with Off_List between them. It should be clear that before Off_List is after Last, and after Off_List is before First. >>Clear effectively deletes all nodes in a list. Therefore, Position_Error >>is propagated if a Position, obtained before Clear is called, is used to >>access the list after Clear is called. >> >>Precondition: not Is_Empty (From) >>Postcondition: not Is_Full (From) > > > Is this a precondition for Clear? Clear on empty list just should do nothing. The conditions for Clear are given after Clear. These are clearly the conditions for Delete. The discussion of propagating Position_Error when using a Position that indicates a deleted node applies to both Clear and Delete. This seemed like the best place to put that discussion, after both Clear and Delete have been described. Perhaps there is a better location; I am certainly open to suggestions. Thanks for your comments. **************************************************************** From: Randy Brukardt Sent: Tuesday, October 1, 2002 5:06 PM >>Clear effectively deletes all nodes in a list. Therefore, Position_Error >>is propagated if a Position, obtained before Clear is called, is used to >>access the list after Clear is called. > The discussion of propagating Position_Error when using a Position that > indicates a deleted node applies to both Clear and Delete. This seemed > like the best place to put that discussion, after both Clear and Delete > have been described. Perhaps there is a better location; I am certainly > open to suggestions. This paragraph seems to be a note about Clear. It isn't even a rule (it follows from other rules), and it certainly doesn't have anything to do with Delete. I'd suggest putting it with Clear, or possibly in the Notes section at the end of the whole subclause. **************************************************************** From: Michael Erdman Sent: Tuesday, October 1, 2002 12:56 PM > function Off_List (List : Handle) return Position; > > This function returns a Position that is valid for List but indicates no > Element in List. This is the same Position returned by Next (Last > (List), List) and by Prev (First (List), List). I am realy wondering what is the gain of this function? **************************************************************** From: Jeffrey Carter Sent: Tuesday, October 1, 2002 1:48 PM It allows the client to know when he has reached the end of a list using Next or Prev. It can also be used to eliminate a recurring function call when adding elements to the ends of the list, as in the example. Some value has to be available for at least the first of these. Perhaps you would like to suggest a different name for the function. **************************************************************** From: Michael Erdman Sent: Tuesday, October 1, 2002 12:56 PM I am realy wondering if a Off_List is realy a position since it is outside! Under this point of view the different behaviour of Append (List, Item, After => Last (List), New_Pos => ...); and Append (List, Item, After => Off_List (List), New_Pos => ...); are not understandable. From my understanding, the last construct should cause an exception, because you append something after nowhere?!! My personal feeling is that last, first are sufficient and off_list is not realy required?! The reat of the AI is fine for me since it lines up with most of the list packages i am using. **************************************************************** From: Randy Brukardt Sent: Tuesday, October 1, 2002 5:29 PM > I am realy wondering if a Off_List is realy a position since it is > outside! Under this point of view the different behaviour of > > Append (List, Item, After => Last (List), New_Pos => ...); > > and > > Append (List, Item, After => Off_List (List), New_Pos => ...); > > are not understandable. From my understanding, the last construct should cause > an exception, because you append something after nowhere?!! I tend to agree with Michael here. It's also annoying that you have to specify the location to Append. It is unfortunate that that parameter cannot default to the end of the list, because that is what is most commonly needed. > My personal feeling is that last, first are sufficient and off_list is not > realy required?! I think that Jeffrey is using Off_List when using Next to iterate over a list. Having First, Last, Next, and Prev raising an exception at the end of the list would complicate that code. You want a loop something like: declare Pos : Position := First(List); begin while Pos /= Off_List(List) loop -- Processing here. Pos := Next(Pos, List); end loop; end loop; You could rewrite the loop to avoid Off_List, but that would require pre-testing for an empty loop (because First would raise an exception in that case): if not Is_Empty(List) then declare Pos : Position := First(List); begin loop -- Processing here. exit when Pos = Last(List); Pos := Next(Pos, List); end loop; end; end if; And you have an exit-in-the-middle loop, which really annoys some pedants. (It doesn't bother me in the least; "while" loops tend to bother me more because they are hard to modify.) **************************************************************** From: Robert I. Eachus Sent: Tuesday, October 1, 2002 7:15 PM I see several problems here. The first is that Append should never take a position parameter. The regular, usual, use of Append will be to insert at the end of the list. Make that interface and code as efficient as possible. It should work on an empty list of course, and raise an exception if the list is alread full, but outside of that, why make the usual case (append at the end) share code with the unusual case (insert in the middle of the list). As for Insert, in that case Insert before an invalid position should insert at the end of the list, and for Insert, a Position parameter makes sense. Next, some nomenclature. Get rid of Handle as a type name. It may actually reflect some implementations, but it won't be correct for all implementations, and if for example, small lists are implemented on the stack, asssignment really should copy the whole list. (You can take 'Access if you want to avoid copy semantics.) The list should be List, or List_Type. If you prefer the later, the name of the parameters can stay as list. Similarly Off_List has the semantics of a value but is only available as a function,. If you really need to have a named value, call it Invalid, and make it a constant. All lists created from a single instance of the package should have the same value for this in any case, since the idea of having different Off_List values for different lists seems very error prone. A farily straightforward implementation has Off_List always returning 0 (zero). There are too many exceptions in the discussion. It makes a great deal of sense to declare the exceptions in the parent package, but there should only be one or two. If two, you should have Position_Error and Full_Structure_Error. Empty_Structure_Error seems to be recreating the Constraint_Error/Numeric_Error problems, again for no good reason. But I personally think that Full_Structure_Error is still one too many. I can see some advantages to having Position_Error separate from Constraint_Error, but Full_Structure_Error looks to me an awful lot like Constraint_Error. Finally, I see no reason to approve half a loaf. If (minimally protected) unprotected lists like this exist, go the extra mile to define the semantics and interface for protected lists. Actually it looks to me like only a few extra yards. Protected lists should be protected on a per list basis, instantiating an iterator should lock the list against writing by other tasks/threads while the iterator exists, and you need an additional generic: generic -- Transaction type Context_Data (<>) is limited private; with procedure Action (List: in out Handle; Context : in out Context_Data; Pos : in out Position); procedure Transaction (List : in out Handle; Context : in out Context_Data; Pos: in out Position); In effect this wraps the Action in a P,V pair, but of course it will probably be implemented with a protected list object. All the rest of the semantics of protected list objects can be described in terms of this generic and the unprotected list type, even if they are not implemented that way. **************************************************************** From: Jeffrey Carter Sent: Tuesday, October 1, 2002 3:57 PM !wording A.X.3 Unprotected Unbounded-Length List Handling The language-defined package Something.List_Unbounded_Unprotected provides a generic package each of whose instances yields a limited private type Handle, a private type Position, and a set of operations. An object of a particular List_Bounded_Unprotected Handle type represents a list whose length can vary conceptually between 0 and a maximum size established by available storage. Objects of type Position are used to indicate values of type Element stored in a list. The subprograms for bounded lists are overloaded for unbounded lists, except Is_Full. Static Semantics The library package Something.List_Unbounded_Unprotected has the following declaration: generic -- Something.List_Unbounded_Unprotected type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Something.List_Unbounded_Unprotected is pragma Preelaborate; type Handle is limited private; type Position is private; Position_Error : exception; procedure Assign (To : out Handle; From : in Handle); procedure Clear (List : in out Handle); -- Operations to obtain valid positions for lists: function First (List : Handle) return Position; function Last (List : Handle) return Position; function Off_List (List : Handle) return Position; -- Operations to obtain valid positions from valid positions: function Next (Pos : Position; List : Handle) return Position; function Prev (Pos : Position; List : Handle) return Position; -- Operations to manipulate lists procedure Insert (Into : in out Handle; Item : in Element; Before : in Position; New_Pos : out Position); procedure Append (Into : in out Handle; Item : in Element; After : in Position; New_Pos : out Position); procedure Delete (From : in out Handle; Pos : in out Position); function Get (From : Handle; Pos : Position) return Element; procedure Put (Into : in out Handle; Pos : in Position; Item : in Element); function Is_Empty (List : Handle) return Boolean; function Length (List : Handle) return Natural; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Pos : in Position; Continue : out Boolean); procedure Iterate (Over : in out Handle; Context : in out Context_Data); private -- Something.List_Unbounded_Unprotected -- not specified by the language end Something.List_Unbounded_Unprotected; A list is a sequence of Elements. Each Element in the sequence may be accessed through a Position; we say that the Position indicates the Element. Elements may be added to, deleted from, accessed, or modified in a list at any position. Using First and Next, a list may be traversed from the first Element to the last; using Last and Prev, from the last Element to the first. An object of type Handle is initially empty. An object of type Position is initially invalid. procedure Assign (To : out Handle; From : in Handle); If To and From are the same list, Assign has no effect. Otherwise, To is cleared and made into a copy of From. If, after clearing To, there is insufficient storage to allocate list nodes for copies of all the Elements in From, Storage_Exhaustion_Error is propagated. Postcondition: Length (To) = Length (From) procedure Clear (List : in out Handle); This procedure makes List empty. Postcondition: Is_Empty (List) Length (List) = 0 function First (List : Handle) return Position; This function returns a Position indicating the first Element in List. If List is empty, this is the same as Off_List (List). function Last (List : Handle) return Position; This function returns a Position indicating the last Element in List. If List is empty, this is the same as Off_List (List). function Off_List (List : Handle) return Position; This function returns a Position that is valid for List but indicates no Element in List. This is the same Position returned by Next (Last (List), List) and by Prev (First (List), List). function Next (Pos : Position; List : Handle) return Position; If Pos is not a valid Position for List, Position_Error is propagated. If Pos = Last (List), this function returns Off_List (List). If Pos = Off_List (List), this function returns First (List). Otherwise, this function returns a Position that indicates the next Element in List after the Element indicated by Pos. function Prev (Pos : Position; List : Handle) return Position; If Pos is not a valid Position for List, Position_Error is propagated. If Pos = First (List), this function returns Off_List (List). If Pos = Off_List (List), this function returns Last (List). Otherwise, this function returns a Position that indicates the previous Element in List before the Element indicated by Pos. procedure Insert (Into : in out Handle; Item : in Element; Before : in Position; New_Pos : out Position); If Before is not a valid Position for Into, Position_Error is propagated. If Into is empty and Before /= Off_List (Into), Position_Error is propagated. If there is insufficient storage to allocate a new list node, Storage_Exhaustion_Error is propagated. If Before = Off_List (List), Item is added as the last Element in Into. Otherwise, Item is inserted into Into before the Element indicated by Before. New_Pos becomes a valid Position for Into that indicates Item. Postcondition: Prev (Before, Into) = New_Pos procedure Append (Into : in out Handle; Item : in Element; After : in Position; New_Pos : out Position); If After is not a valid Position for Into, Position_Error is propagated. If Into is empty and After /= Off_List (Into), Position_Error is propagated. If there is insufficient storage to allocate a new list node, Storage_Exhaustion_Error is propagated. If After = Off_List (List), Item is added as the first Element in Into. Otherwise, Item is inserted into Into after the Element indicated by After. New_Pos becomes a valid Position for Into that indicates Item. Postcondition: Next (After, Into) = New_Pos procedure Delete (From : in out Handle; Pos : in out Position); If From is empty, Something.Empty_Structure_Error is propagated. If Pos is not a valid Position for From or Pos = Off_List (From), Position_Error is propagated. Otherwise, the Element indicated by Pos is removed from From. Pos is made invalid. Elements are stored in "nodes" in a list. A Position indicates an Element by indicating its node. One Position may indicate a node that has been deleted through another Position. Position_Error is propagated if such a Position is subsequently used to access the list. Clear effectively deletes all nodes in a list. Therefore, Position_Error is propagated if a Position, obtained before Clear is called, is used to access the list after Clear is called. Precondition: not Is_Empty (From) function Get (From : Handle; Pos : Position) return Element; If Pos is not a valid Position for From or Pos = Off_List (From), Position_Error is propagated. Otherwise, this function returns the Element in From indicated by Pos. Precondition: not Is_Empty (From) procedure Put (Into : in out Handle; Pos : in Position; Item : in Element); If Into is empty, Something.Empty_Structure_Error is propagated. If Pos is not a valid Position for Into or Pos = Off_List (Into), Position_Error is propagated. Otherwise, the Element in Into indicated by Pos is made to be Item. Precondition: not Is_Empty (Into) function Is_Empty (List : Handle) return Boolean; This function returns True if List is empty and False otherwise. function Length (List : Handle) return Natural; This function returns the number of Elements stored in List. generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Pos : in Position; Continue : out Boolean); procedure Iterate (Over : in out Handle; Context : in out Context_Data); An instance of this procedure applies Action to each Element stored in Over and its Position in turn, from first to last. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a list while iterating over it. Possible consequences are: propagating Position_Error, propagating Constraint_Error, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. Implementation Permission An implementation may reuse deleted nodes, even if that means it cannot guarantee that Position_Error will be propagated when a Position that indicates a deleted node is used to access the list. Implementation Requirements No storage associated with a Handle object shall be lost upon scope exit, a call to Clear or Delete, or being associated with the parameter To of a call to Assign. !discussion Unbounded lists have the same interface as bounded lists, except for the absence of Is_Full and the propagating of Storage_Exhaustion_Error rather than Full_Structure_Error. The intended behavior of Position checking is the same as for bounded lists. In the reference implementation, there is an explicit off-list node for each list (without space for an Element). The access value designating this node is used as the list ID. The reference implementation of Something.List_Unbounded_Unprotected is PragmARC.List_Unbounded_Unprotected; it differs in its name and the names of the exceptions. It also has a Sort procedure not specified here. Type Handle is a controlled type in the reference implementation. The example below is identical to the bounded list example, except for the name of the generic package and the absence of a discriminant on Handle. !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 1,000 or fewer integers, one per line, read all of the values in Input and store them in a list in the order read. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_List is new Something.List_Unbounded_Unprotected (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Pos : in Integer_List.Position; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_List.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; List : Integer_List.Handle; Pos : Integer_List.Position; Off_List : constant Integer_List.Position := Integer_List.Off_List (List); use Integer_List; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Insert (Into => List, Before => Off_List, Item => I, New_Pos => Pos); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Length (List) ) ); Put_All (Over => List, Context => Dummy); end; **************************************************************** From: Christoph Grein Sent: Wednesday, October 2, 2002 1:51 AM > A.X.3 Unprotected Unbounded-Length List Handling My comments for Unprotected Bounded-Length List about being counter-intuitive also apply to this unbounded variant. loop Get (Item); Append (List, Item, After => Off_List (List), New_Pos => ...); end list; which seems most obvious, you get, contrary to intuition, the list in reverse order! :-( Yuck! I can see the intent with wrapping around via Off_List, but I strongly dislike the consequence of counter-intuition :-( I do think (like Robert I. Eachus) that Append should have no After and simply add at the end. It's the most common use of a list. Insert has a Before and thus can do everything you want: Insert Before First creates a list in inverse order; Insert Before Off_List inserts at the end, i.e. in natural order (i.e. the same as the simplified Append). The idea of wrapping round via Off_List for iterators is good and unaffected by the proposed change to Append. **************************************************************** From: Robert I. Eachus Sent: Wednesday, October 2, 2002 1:00 PM Jeffrey Carter wrote: > The language-defined package Something.List_Unbounded_Unprotected > provides a generic package each of whose instances yields a limited > private type Handle, a private type Position, and a set of operations.... I am going to stick my neck out here and suggest that the parent package be Ada.Lists. Ada.Lists should contain the declarations of all necessary exceptions. The problem with putting exceptions in generics is that each instance of the generic creates a new exception, so it is better to declare the exceptions elsewhere, and if necessary use renaming for visibility. (In this case, I don't see any need for the renamings, but I don't feel too strongly one way or the other.) Ada.Lists should be the parent of four generic packages: Bounded, Unbounded, Bounded_Protected, and Unbounded_Protected. I don't like putting Unprotected in the name, as it implies things that are not necessarily true. (Either the usage of the "unprotected" lists may be totally free of tasking issues, or the implementation may do the minimal protection necessary (creation of new lists) for safety in a particular program. Also for political reasons. Look at all the troubles caused by the name of Unchecked_Deallocation, even though most uses are carefully validated by the programmer. **************************************************************** From: Matthew Heaney Sent: Wednesday, October 2, 2002 1:29 PM The Charles library has the kind of structure you're suggesting: charles.lists.unbounded Eventually there'll be another one: charles.lists.bounded http://home.earthlink.net/~matthewjheaney/charles/index.html I wouldn't bother with protected forms, for the same reasons you didn't try to protect the random number generator type. **************************************************************** From: Matthew Heaney Sent: Wednesday, October 2, 2002 2:14 PM Oops -- I meant: charles.lists.single.unbounded charles.lists.double.unbounded **************************************************************** From: Jeffrey Carter Sent: Wednesday, October 2, 2002 1:53 PM Robert I. Eachus wrote: > I am going to stick my neck out here and suggest that the parent package > be Ada.Lists. Ada.Lists should contain the declarations of all > necessary exceptions. The problem with putting exceptions in generics > is that each instance of the generic creates a new exception, so it is > better to declare the exceptions elsewhere, and if necessary use > renaming for visibility. (In this case, I don't see any need for the > renamings, but I don't feel too strongly one way or the other.) The names of the packages, it seems to me, should be up to the ARG. Clearly the hierarchy should not be named Something. Only lists have positions, so it did not seem like a good idea to put Position_Error in Something with the other exceptions. It may be a good idea to put it outside the list packages, though. I intend to propose queues, stacks, bags, and skip lists as well as lists, so Ada.Lists might not be the best name for the parent package unless the ARG decides to only include lists. **************************************************************** From: Matthew Heaney Sent: Wednesday, October 2, 2002 2:18 PM All you need is another intermediate package, ie ada.containers.lists... ada.containers.maps... **************************************************************** From: Simon J. Wright Sent: Thursday, October 3, 2002 10:37 AM If there is any chance of different sorts of container, then you would want something more like Ada.Containers.Lists Ada.Containers.Maps and then the exceptions would be in one of Ada.Containers (where the Booch Components put them) Ada.Containers.Exceptions Ada.Container_Exceptions (like IO_Exceptions) **************************************************************** From: Tucker Taft Sent: Thursday, October 3, 2002 12:40 PM This does look like a reasonable organization, and I would put the exceptions in the parent package, Ada.Containers, so there is no temptation to do the tedious renaming that all the various IO packages ended up doing. **************************************************************** From: Jeffrey Carter Sent: Thursday, October 3, 2002 1:46 PM I would think that exceptions specific to one branch of this tree should go lower down. For example, Position_Error only applies to lists, and so would go into Ada.Data_Structures.Lists. **************************************************************** From: Randy Brukardt Sent: Friday, October 4, 2002 5:37 PM Jeffrey Carter wrote: > The names of the packages, it seems to me, should be up to the ARG. > Clearly the hierarchy should not be named Something. That's not quite the right attitude. You're making a proposal, and that clearly should include the name of the hierarchy and its position. Pick something (I prefer Ada.Containers of the suggestions I've seen) and stick with it. After all, the ARG can change anything about any proposal. But we don't have the time to spend sitting around thinking up the best names for things. I can easily image us sitting around for hours arguing about the name of this hierarchy -- I'd rather not have to go there. **************************************************************** From: Robert Duff Sent: Saturday, October 5, 2002 8:39 AM I can imagine us sitting around for an hour arguing about *whether* to argue about the name, or whether to go eat lunch instead. *Then* we go to lunch (because now it's past lunch time). *Then* we do as Randy says above. ;-) ;-) ;-) Anyway, Randy's right: you (Jeffrey) pick a name, and see if it sticks. I can live with your "Ada.Data_Structures" idea, or I can live with "Ada.Containers". **************************************************************** From: Jeffrey Carter Sent: Saturday, October 5, 2002 7:33 PM > I can imagine us sitting around for an hour arguing about *whether* to > argue about the name, or whether to go eat lunch instead. *Then* we go > to lunch (because now it's past lunch time). *Then* we do as Randy > says above. Sounds like an efficient organization. > Anyway, Randy's right: you (Jeffrey) pick a name, and see if it sticks. > I can live with your "Ada.Data_Structures" idea, or I can live with > "Ada.Containers". OK, I'll be using Ada.Data_Structures until further notice. **************************************************************** From: Tucker Taft Sent: Saturday, October 5, 2002 7:45 PM I prefer "Ada.Containers". "Ada.Data_Structures" is too "generic" ;-). "Containers" seems to be the term the industry has adopted for data structures that are used for holding lists, sets, maps, etc., of objects. It makes sense to follow the crowd in this case, IMHO. **************************************************************** From: Robert Duff Sent: Sunday, October 6, 2002 12:48 PM > Sounds like an efficient organization. Well, you snipped my smileys -- I was exagerating, of course. The *real* reason it takes years for the ARG to take action is that the members are volunteers who can't afford to spend much more than a few days per year doing ARG work. Volunteers (like you) submitting fully worked-out proposals are a big help. **************************************************************** From: Robert Dewar Sent: Wednesday, October 2, 2002 1:38 AM I think it would be useful to have a focused discussion on whether such a package is appropriate at all to a new standard. Failing agreement on this issue, there is not much point in discussing details of a particular design. In fact I would say that we should have a high level discussion and list of all desirable package additions before any detailed design is done. Otherwise you get a disorganized process in which everyone submits their fabvorite packages (for a collection of our favorites, see GNAT.xxx :-) **************************************************************** From: Randy Brukardt Sent: Wednesday, October 2, 2002 6:25 PM Robert wrote: > I think it would be useful to have a focused discussion on whether such > a package is appropriate at all to a new standard. Failing agreement on > this issue, there is not much point in discussing details of a particular > design. We discussed container libraries (AI-302) briefly in Vienna. No one objected to the general idea. > In fact I would say that we should have a high level discussion and > list of all desirable package additions before any detailed design is done. This was done quite a few meetings back. The ARG essentially decided that it did not have enough information to decide on which packages are appropriate to add. And we definitely do not want to be in the business of designing packages. Rather, we decided to ask the community to propose packages. This was in the call for APIs (http://www.adaic.org/news/call4apis.html). > Otherwise you get a disorganized process in which everyone submits their > favorite packages (for a collection of our favorites, see GNAT.xxx :-) Well, that has not happened to date. That's because a submission of a serious proposal requires RM-style wording, and that's a lot of work. A proposal containing only the specs and comments from several GNAT packages would be round-filed. OTOH, proper proposals based on GNAT packages would be looked at seriously, as they clearly meet the goal of "available for years". **************************************************************** From: Ehud Lamm Sent: Thursday, October 3, 2002 9:32 AM > Well, that has not happened to date. That's because a submission of a > serious proposal requires RM-style wording, and that's a lot > of work. A few of us are working toward that goal. The group started following the Ada-Europe workshop concerning a standard container library for Ada. I created a mailing list for our discussions, so as not to disturb the discussions on Ada-Comment. When we have a decent AI to propose, we will of course submit it. If anyone is interested in joing the mailing list, let me know. **************************************************************** From: Matthew Heaney Sent: Monday, October 7, 2002 9:34 AM > If I get to choose, I'd pick Ada.Data_Structures. I'm not sure where > "containers" came from. I've taken courses on data structures, and read > books like "Data Structures + Algorithms = Programs". I've never seen > similar courses or books on containers. There is a different emphasis. A "data structure" is a low-level entity, with a specific representation. A "container" is more high-level, something you'd describe abstractly, in terms of its time and space properties. For example, an STL set is a "container" that is implemented using a red-black tree "data structure." (You could use other data structures as well: an AVL tree, or a hash table, for example.) > Should I begin using this in future updates to the AI? I plan on submitting my own proposal for AI-302, using the subsystem name "ada.containers". **************************************************************** From: Jeffrey Carter Sent: Monday, October 7, 2002 2:32 PM In the data-structures courses I took and the data-structures texts I have read, a data structure is an abstraction with multiple possible implementations. For example, the abstraction called a list can be implemented singly or doubly linked, bounded or unbounded, and protected or unprotected, yielding 8 implementations of the same abstraction. Describing this list abstraction in terms of its time and space properties is difficult. Many of these are properties of a specific implementation. For example, a length function can be O(1) or O(N) in time, depending on the implementation. A previous position function is O(N) for a singly linked list, but O(1) for a doubly linked implementation. > I plan on submitting my own proposal for AI-302, using the subsystem > name "ada.containers". Great news. **************************************************************** From: Robert Dewar Sent: Monday, October 7, 2002 2:37 PM << There is a different emphasis. A "data structure" is a low-level entity, with a specific representation. A "container" is more high-level, something you'd describe abstractly, in terms of its time and space properties. >> Please avoid the use of the term data structure, since it has so many meanings at various level of abstraction (the above definition is not one that many people would accept!) **************************************************************** From: Pascal Obry Sent: Tuesday, October 8, 2002 2:45 PM Tucker Taft writes: > I prefer "Ada.Containers". "Ada.Data_Structures" is Agreed. This is also the name I was about to propose. **************************************************************** From: Jeffrey Carter Sent: Tuesday, October 8, 2002 4:36 PM !wording A.X.4 Unprotected Unbounded Bag Handling The language-defined package Ada.Data_Structures.Bag_Unbounded_Unprotected provides a generic package each of whose instances yields a limited private type Handle and a set of operations. An object of a particular Bag_Unbounded_Unprotected Handle type represents a bag whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Data_Structures.Bag_Unbounded_Unprotected has the following declaration: generic -- Ada.Data_Structures.Bag_Unbounded_Unprotected type Element is private; with function "=" (Left : Element; Right : Element) return Boolean is <>; package Ada.Data_Structures.Bag_Unbounded_Unprotected is pragma Preelaborate; type Handle is limited private; procedure Assign (To : out Handle; From : in Handle); procedure Clear (Bag : in out Handle); procedure Add (Item : in Element; Into : in out Handle); procedure Delete (Item : in Element; From : in out Handle); procedure Update (Item : in Element; Bag : in out Handle); type Find_Result (Found : Boolean := False) is record case Found is when False => null; when True => Item : Element; end case; end record; function Find (Key : Element; Bag : Handle) return Find_Result; function Is_Empty (Bag : Handle) return Boolean; function Size (Bag : Handle) return Natural; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Handle; Context : in out Context_Data); private -- Ada.Data_Structures.Bag_Unbounded_Unprotected -- not specified by the language end Ada.Data_Structures.Bag_Unbounded_Unprotected; A bag is an unordered collection of Elements. Elements stored in a bag may be searched for, updated, and deleted if they match a user-supplied Element according to the generic formal function "=". Actual functions supplied for "=" often compare only part of an Element (the "key") to obtain their result. An object of type Handle is initially empty. procedure Assign (To : out Handle; From : in Handle); If To and From are the same bag, Assign has no effect. Otherwise, To is cleared and made into a copy of from. If, after clearing To, there is insufficient storage for copies of all the Elements in From, Storage_Exhaustion_Error is propagated. Postcondition: Size (To) = Size (From) procedure Clear (Bag : in out Handle); Clear makes Bag empty. Postcondition: Is_Empty (Bag) Size (Bag) = 0 procedure Add (Item : in Element; Into : in out Handle); If there is insufficient storage available to store Item in From, Storage_Exhaustion_Error is propagated. Otherwise, Add adds Item to Into. Postcondition: not Is_Empty (Into) Size (Into) = 0 procedure Delete (Item : in Element; From : in out Handle); If From contains an Element X such that X = Item, Delete deletes X from From. Otherwise, Delete has no effect. If From contains more than one such Element, Delete deletes one of these Elements. Which of these Elements is deleted is undefined. procedure Update (Item : in Element; Bag : in out Handle); If Bag contains an Element X such that X = Item, Update conceptually performs X := Item. Otherwise, Update has no effect. If Bag contains more than one such Element, Update updates one of these Elements. Which of these Elements is updated is undefined. Type Find_Result defines the results of function Find. function Find (Key : Element; Bag : Handle) return Find_Result; If Bag contains an Element X such that X = Key, Find returns (Found => True, Item => X). Otherwise, Find returns (Found => False). If Bag contains more than one such Element, Find returns one of these Elements as the Item component of the result. Which of these Elements is returned is undefined. function Is_Empty (Bag : Handle) return Boolean; Is_Empty returns True if Bag contains no elements and False otherwise. function Size (Bag : Handle) return Natural; Size returns the number of elements stored in Bag. generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Handle; Context : in out Context_Data); An instance of Iterate applies Action to each Element in Over. The order in which the Elements in Over are processed is undefined. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a bag while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. Implementation Requirements No storage associated with a Handle object shall be lost upon scope exit, a call to Clear or Delete, or being associated with the parameter To of a call to Assign. AARM Note This package could be implemented using an instantiation of Ada.Data_Structures.List_Unbounded_Unprotected. !discussion The reference implementation of Ada.Data_Structures.Bag_Unbounded_Unprotected is PragmARC.Bag_Unbounded_Unprotected; it differs in its name and uses Empty rather than Is_Empty. Type Handle is a wrapper around a List_Unbounded_Unprotected Handle in the reference Implementation. !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 0 or more integers, one per line, read all of the values in Input and store them in a bag. Once the entire file has been read, output the number of values read followed by all the values to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Bag is new Ada.Data_Structures.Bag_Unbounded_Unprotected (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_Bag.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; Bag : Integer_Bag.Handle; use Integer_Bag; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Add (Item => I, Into => Bag); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Size (Bag) ) ); Put_All (Over => Bag, Context => Dummy); end; **************************************************************** From: Christoph Grein Sent: Wednesday, October 9, 2002 6:42 PM > procedure Add (Item : in Element; Into : in out Handle); > > If there is insufficient storage available to store Item in From, > Storage_Exhaustion_Error is propagated. > > Otherwise, Add adds Item to Into. > > Postcondition: not Is_Empty (Into) > Size (Into) = 0 Size (Into) > 0 **************************************************************** From: Jeffrey Carter Sent: Wednesday, October 9, 2002 2:03 PM Right. Thanks for spotting that. This made me look again at the bounded list proposal, and I found a similar error: For Assign, the precondition should be To.Max_Size >= Length (From) **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 7:12 AM <<> If there is insufficient storage available to store Item in From, > Storage_Exhaustion_Error is propagated.>> Why not just storage_error with an appropriate message, proliferation of exceptions is a menace. **************************************************************** From: Martin Dowie Sent: Thursday, October 31, 2002 1:54 AM > If there is insufficient storage available to store Item in the queue, > Storage_Exhaustion_Error is propagated. Again, what is the advantage of this over just raising "Storage_Error"? **************************************************************** From: Robert Dewar Sent: Thursday, November 7, 2002 7:26 PM I also prefer to just generate Storage_Error instead of inventing a new exception. Remember that in any respectable implementation there will be a message with the exception giving details. **************************************************************** From: Alexandre E. Kopilovitch Sent: Wednesday, October 9, 2002 11:41 AM Just wonder, to which Annex the proposed Ada.Container packages should belong? (I suppose they should not go to the Annex A, "Predefined Language Environment", because containers certainly do not belong to "language environment".) **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 11:32 PM Why not? Annex A is just a miscellaneous collection of supposedly useful stuff, if containers belong anywhere they belong there. **************************************************************** From: Alexandre E. Kopilovitch Sent: Wednesday, October 9, 2002 12:26 PM Not at all. Look at the contents of Annex A - all its parts really deal with the language environment, they standardize Ada's contacts with an external realities such as alphabets, files, some basic numeric issues etc. There is single exception - Strings, but some basic Strings are necessary for other parts of this Annex, so presence of Strings here is justified. (And at the same time, the Strings as containers - that is, Fixed/Bounded/Unbounded_Strings, which are placed here to be near to the basic Strings, provoke some dissatisfaction and repeated discussions.) > if containers belong anywhere they belong there. But containers are pure abstractions, they do not deal directly with external realities. Why should they be called part of language enviroment? And at the same time they aren't simply some miscellanous useful stuff, the containers are quite recognized general entities. **************************************************************** From: Robert A. Duff Sent: Wednesday, October 9, 2002 2:26 PM I agree with Robert -- Annex A is a fine place for container packages (if they belong in the Standard at all). Annex A is called "Prdefined Language Environment". The word "environment" is used in the sense of RM-10.1.4(1) and elsewhere in chap 10 -- basically just a bunch of available library units. So "predefined environment" is just the library units come along with the Standard. The term "environment" here was not meant to imply interfacing with the environment outside of Ada -- what I would call the "external environment" (like I/O packages that interface with a file system, and the like). It's just happens to be the case that many parts of the "predefined environment" involve interfaces to the "external environment" -- two different meanings of the English word "environment". The first page of Annex A lists all the library units in the predefined environment. Note that some of these are actually defined elsewhere. For example, package System is part of the predefined environment, but it is defined in chap 13, where interface-to-hardware and similar stuff goes. And Ada.Calendar is defined in chap 9, along with other tasking-related stuff. I say, if the container classes are "big", they should get their own Annex. Otherwise, they should go in Annex A. Anyway, I think it's premature to spend a lot of time arguing about where the container packages go. We have not yet decided that they should be in the Standard at all, and we have not yet decided what they should look like. P.S. During the Ada 9X design, I argued that the I/O stuff should get its own Annex, because it's "big". Not for any logical classification reasons. I lost that argument. ;-) But anyway, Robert is right that Annex A is just a miscellaneous collection of useful "stuff". **************************************************************** From: Robert Dewar Sent: Tuesday, October 8, 2002 4:46 PM I must say that my reaction to this set of proposed container packages is "nice, but why bother to include any of them in the standard, and give them special status". I think it is fine to have such packages around, but I would let the market place decide whether they are useful. In particular, if a package is to be considered, it would be very valuable to know that it had already been widely used. If it has not been widely used, then you have to ask why not? And assume the answer is because it is not that successful a proposal. **************************************************************** From: Matthew Heaney Sent: Tuesday, October 8, 2002 5:22 PM Robert's comment reminds me of something I read in one of Jerry Weinberg's books, about a phenomenon called the "Railroad Paradox." A group of commuters would like to take a train into the city at 2:30, but are forced to take the train at other times, because there are no trains that run at 2:30. So they ask the railroad administrator if the railroad company wouldn't mind running a train at that hour. And the railroad person says: We can run a train at that hour, but only if there is enough demand. A few days pass, and the commuters ask the railroad what it has decided, and were told that no, they wouldn't be running a train at that hour. The commuters then ask: What was your reason for that decision? And the man says: Well, I went to the platform every day at 2:30, and no one was waiting around for a train. So he concluded that there was no demand. **************************************************************** From: Robert Dewar Sent: Tuesday, October 8, 2002 7:15 PM The comparison is inapt. Our customers don't care two hoots whether packages are or are not standard. As you know we provide a lot of packages in the GNAT hierarchy, and the ones we provide are a combination of those that our customers ask for, plus in some cases packages we write for our own use that may as well be made reusable. Customers have often asked for functionality, and are certainly not shy in doing so. We currently don't provide much in the way of container support since there has been no perceptible demand. Perhaps our customers are atypical in some way. But I trust much more the collective indication of value from our customers to determine what is useful than I trust some intution of Matthew Heaney or any other individual. Now if you (Matthew or anyone else) tell me "well this package was developed to meet the needs of user xxx, and has been in effective use by yyy and zzz for nnn years", then that has force. But if you can't convince anyone to use your package, it seems unreasonable to try to force implementors to support it by adding it to the standard. If these packages are useful, then the proper first step, well before standardizing them is to make them generally available and invite beta testing by real users. If you can't get real users to beta test or otherwise show interest in the package you have proposed, then why should the ARG be interested. It is NOT our job in the ARG to invent needs, it is our job to react to needs! **************************************************************** From: Pascal Obry Sent: Wednesday, October 9, 2002 2:37 AM > I must say that my reaction to this set of proposed container > packages is "nice, but why bother to include any of them in > the standard, and give them special status". I think it is > fine to have such packages around, but I would let the market > place decide whether they are useful. > I disagree. It is a pain to have to distribute components taken from the Web with your application. It would be not just nice but very clean to have a proper set of containers. In most project I'm working we need such components: List, Queue, Stack, Table... The market has certainly already chosen but it will be hard to know which set of containers are the most widely used. I used a lot the components from LGL: http://lglwww.epfl.ch/ada/components/home_page.html, and I use also some other separate components... The problem here is that all these are not using the same naming convention, way of dealing with the API (exception, returning boolean...) It does not look very uniform. So yes I think that a set of nice components, from the same source, with the same design will be a very good addition for Ada. **************************************************************** From: Pascal Obry Sent: Wednesday, October 9, 2002 3:16 AM Robert Dewar writes: > We currently don't provide much in the way of container support since there > has been no perceptible demand. Perhaps our customers are atypical in some > way. Or maybe they are using the containers found on the Web. This does not mean that they don't want a more standard distribution. > It is NOT our job in the ARG to invent needs, it is our job to react to > needs! Agreed, but here we are talking about generic containers. The need has been there since the start of the computer science. **************************************************************** From: Pascal Obry Sent: Wednesday, October 9, 2002 3:18 AM Robert Dewar writes: > It is NOT our job in the ARG to invent needs, it is our job to react to > needs! Maybe what you are saying is that we should keep these components as simple as possible. Good old generic version of the most widely known containers like Lists, Queues, Stacks, Associative Tables... **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 7:00 AM No, that is *not* what I am saying. There are two approaches to standardization. Think up languages and features out of the blue, standardize, then let the world test the standard by using it (examples: Algol-60, Ada). Use languages and features informally, standardize proved technology after the act (examples: C, C++, Fortran). What I am saying is that for container packages the second approach is more appropriate than the first **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 6:48 AM <> I was complaining that as far as I can see the packages being proposed here are out of the blue and have *no* track record of usage. Yes, of course if we choose some widely used components it is reasonable to clean them up wrt naming etc. Note incidentally that in this case, it is very important to clear IPR's properly with the author. The fact that something that is widely used looks like it has appropriate licensing is legally meaningless. In general if you download something free from the web you have no idea what the IPR situation is for serious use. But I really think that it is the price of admission for packages like this to have been tested in actual use and found useful. Such packages will get only very light scrutiny during any standardization process (this is certainly true of the packages that were included in the Ada 95 standard, they received far less scrutiny than other features in the language, simply because fewer people are interested, and those who are interested tend to be more applications oriented, which is fine for looking at general usability but not fine for careful semantic analysis). Given that they will get light scrutiny, the last thing we want is that we get a big bunch of packages that have not been tested by actual use, and that represent what a small number of people *think* might be useful, without wide scrutiny. It seems a perfectly reasonable criterion that we only consider packages that have indeed been used. <> I certainly am not talking about miscellaneous packages distributed from "the Web" here. Indeed as I note above, such distribution is frought with serious IPR concerns. I am talking about commercial packages that are properly supported. If there are no such from any vendor, then you really have to wonder why the ARG is pushing to standardize things that no vendor has found it worthwhile to provide. Given that it is so easy to provide packages (unlike the situation of providing language extensions), then you really have to wonder. As I mentioned, in the GNAT project, we all the time add packages to our GNAT hierarchy (there is a list of the packages appended below) and most of our users regard them as essentially equivalent to language supplied packages. Certainly they are not "distributing these from the web". Container packages simply have not come up. If I search our database of suggestions from customers, which contains hundreds of entries, including many suggestions for additional packages, the word containers does not appear. The word stack does occur, but only in connection with the activation stack (e.g. provide method of determining stack usage :-) **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 6:57 AM <> Fine, let's start with these "containers found on the Web" as a starting point, and be connected with the usage by these customers. Actually very few of our customers download stuff from the Web with dubious IPR status, so I don't think you will find many examples. We had a couple of customers trying to use some version of the Booch components at one point. But if these customers who are using containers found on the web wanted a more standard distribution, they would in general suggest that to us. We have lots of suggestions for additional packages in our files, including stop watch function byte swapping routines large integer support shared memory support fp trap handling package altivec support package etc. Here is the list of packages in GNAT, most of which came from user requests and suggestions: * Ada.Characters.Latin_9 (a-chlat9.ads):: * Ada.Characters.Wide_Latin_1 (a-cwila1.ads):: * Ada.Characters.Wide_Latin_9 (a-cwila9.ads):: * Ada.Command_Line.Remove (a-colire.ads):: * Ada.Command_Line.Environment (a-colien.ads):: * Ada.Direct_IO.C_Streams (a-diocst.ads):: * Ada.Exceptions.Is_Null_Occurrence (a-einuoc.ads):: * Ada.Sequential_IO.C_Streams (a-siocst.ads):: * Ada.Streams.Stream_IO.C_Streams (a-ssicst.ads):: * Ada.Strings.Unbounded.Text_IO (a-suteio.ads):: * Ada.Strings.Wide_Unbounded.Wide_Text_IO (a-swuwti.ads):: * Ada.Text_IO.C_Streams (a-tiocst.ads):: * Ada.Wide_Text_IO.C_Streams (a-wtcstr.ads):: * GNAT.AWK (g-awk.ads):: * GNAT.Bubble_Sort (g-bubsor.ads):: * GNAT.Bubble_Sort_A (g-busora.ads):: * GNAT.Bubble_Sort_G (g-busorg.ads):: * GNAT.Calendar (g-calend.ads):: * GNAT.Calendar.Time_IO (g-catiio.ads):: * GNAT.CRC32 (g-crc32.ads):: * GNAT.Case_Util (g-casuti.ads):: * GNAT.CGI (g-cgi.ads):: * GNAT.CGI.Cookie (g-cgicoo.ads):: * GNAT.CGI.Debug (g-cgideb.ads):: * GNAT.Command_Line (g-comlin.ads):: * GNAT.Ctrl_C (g-ctrl_c.ads):: * GNAT.Current_Exception (g-curexc.ads):: * GNAT.Debug_Pools (g-debpoo.ads):: * GNAT.Debug_Utilities (g-debuti.ads):: * GNAT.Directory_Operations (g-dirope.ads):: * GNAT.Dynamic_Tables (g-dyntab.ads):: * GNAT.Exception_Traces (g-exctra.ads):: * GNAT.Expect (g-expect.ads):: * GNAT.Float_Control (g-flocon.ads):: * GNAT.Heap_Sort (g-heasor.ads):: * GNAT.Heap_Sort_A (g-hesora.ads):: * GNAT.Heap_Sort_G (g-hesorg.ads):: * GNAT.HTable (g-htable.ads):: * GNAT.IO (g-io.ads):: * GNAT.IO_Aux (g-io_aux.ads):: * GNAT.Lock_Files (g-locfil.ads):: * GNAT.MD5 (g-md5.ads):: * GNAT.Most_Recent_Exception (g-moreex.ads):: * GNAT.OS_Lib (g-os_lib.ads):: * GNAT.Perfect_Hash.Generators (g-pehage.ads):: * GNAT.Regexp (g-regexp.ads):: * GNAT.Registry (g-regist.ads):: * GNAT.Regpat (g-regpat.ads):: * GNAT.Sockets (g-socket.ads):: * GNAT.Source_Info (g-souinf.ads):: * GNAT.Spell_Checker (g-speche.ads):: * GNAT.Spitbol.Patterns (g-spipat.ads):: * GNAT.Spitbol (g-spitbo.ads):: * GNAT.Spitbol.Table_Boolean (g-sptabo.ads):: * GNAT.Spitbol.Table_Integer (g-sptain.ads):: * GNAT.Spitbol.Table_VString (g-sptavs.ads):: * GNAT.Table (g-table.ads):: * GNAT.Task_Lock (g-tasloc.ads):: * GNAT.Threads (g-thread.ads):: * GNAT.Traceback (g-traceb.ads):: * GNAT.Traceback.Symbolic (g-trasym.ads):: * Interfaces.C.Extensions (i-cexten.ads):: * Interfaces.C.Streams (i-cstrea.ads):: * Interfaces.CPP (i-cpp.ads):: * Interfaces.Os2lib (i-os2lib.ads):: * Interfaces.Os2lib.Errors (i-os2err.ads):: * Interfaces.Os2lib.Synchronization (i-os2syn.ads):: * Interfaces.Os2lib.Threads (i-os2thr.ads):: * Interfaces.Packed_Decimal (i-pacdec.ads):: * Interfaces.VxWorks (i-vxwork.ads):: * Interfaces.VxWorks.IO (i-vxwoio.ads):: * System.Address_Image (s-addima.ads):: * System.Assertions (s-assert.ads):: * System.Memory (s-memory.ads):: * System.Partition_Interface (s-parint.ads):: * System.Task_Info (s-tasinf.ads):: * System.Wch_Cnv (s-wchcnv.ads):: * System.Wch_Con (s-wchcon.ads):: **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 7:02 AM By the way, Pascal's point that queues etc have been around "in computer science" [I would prefer a reference to applications and software engineering here I must say :-)] is not relevant. Lots of stuff has been around for much longer, e.g. matrix diagonalization, and there are loads of md routines that have been around and are being used, but I don't see anyone wanting to stuff those into Ada (partly this is because the ARG community is pretty sparse on anyone with an interest in numerical matters :-) **************************************************************** From: Pascal Obry Sent: Wednesday, October 9, 2002 8:19 AM Maybe I'm biased because I'm on the IT field (Yes I use Ada for non safety critical stuff mostly for Web developement :) and I really don't see how to live without a nice list or associative table component. **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 8:31 AM Well most certainly the use of Ada for Web development is entirely non typical usage :-) As for living "without a nice list". In fact creating customized list structures is trivial in practice, and I think most people find that it is easier and better to create abstractions that are exactly tailored to the needs than to try to reuse simple components, devoid of any algorithmic complexity. Now if you talk about an indexed B-tree container with leading/trailing key compression, the situation is quite different, because there is in that case significant algorithmic content. But a "nice list" is a very uncompelling example to me. I just for example recreated the Scheme view of lists in a little Ada package. It took me about 15 minutes (I wanted it for my class this afternoon). This is really not rocket science. **************************************************************** From: Pascal Obry Sent: Wednesday, October 9, 2002 8:42 AM Robert Dewar writes: > Well most certainly the use of Ada for Web development is entirely non > typical usage :-) I know :) > As for living "without a nice list". In fact creating customized list > structures is trivial in practice, and I think most people find that > it is easier and better to create abstractions that are exactly tailored > to the needs than to try to reuse simple components, devoid of any > algorithmic complexity. Well re-doing something is always a risk of introducing a bug. Even in a simple list. A list well tested is not to be tested again. You can rely on it to be memory leak free for example... There is always some tricky part even in a linked list. That's my point. Testing is a nightmare, a known-to-be-safe list is a relief ! > Now if you talk about an indexed B-tree container with leading/trailing > key compression, the situation is quite different, because there is in > that case significant algorithmic content. Agreed. **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 9:08 AM Sorry, I can't be convinced, I just expanded my scheme in Ada example to several hundred lines of code in 30 mins and it all worked first time (that's because it is trivial :-) Once again, a simple list is not a good example for me. Now if it is a controlled list (you mentioned memory leaks), then it is a very heavy object, not suitable for most uses of lists in practice. **************************************************************** From: Pascal Obry Sent: Wednesday, October 9, 2002 11:07 AM This is too strange. One big strength of Ada is the possibility to build reusable components. You are just saying that you want something just do it. Of course I can do it, of course I can do it fast, I'm not sure I can do this fast, right and safe at the same time. And what the point in loosing 30 minutes per components in every single project !!! Maybe it is fine for your course, but for sure I'll have to spend more than 30 minutes in a linked-list (or whatever containers) for a software that I need to deliver to customer. Let me return to you that I can't be convinced :) **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 12:02 PM I am saying that trivial reusable components (a simple linked list) do not make an effective case for reuse. If that's all we come up with, I would say the container effort is a waste of time. **************************************************************** From: Pascal Leroy Sent: Wednesday, October 9, 2002 12:28 PM > We currently don't provide much in the way of container support since there > has been no perceptible demand. Perhaps our customers are atypical in some > way. Many of our customers use one flavor or another of the Booch components, but then our customers are surely atypical in this respect, because for many years we actively promoted the Booch components. These components may not be perfect, but at least we know that they have been intensively used by many projects for many years. > If these packages are useful, then the proper first step, well before > standardizing them is to make them generally available and invite beta > testing by real users. If you can't get real users to beta test or otherwise > show interest in the package you have proposed, then why should the ARG > be interested. I couldn't agree more. I believe that for things like containers where there are already a number of libraries around which have been in use for a long time, it's much more productive to standardize _existing_ packages (be it Booch or anything else) than to invent new ones and at the same time try to standardize them. Of course, that reduces the standardization work to a "mere" documentation effort, and that may not be as exciting as cranking out new specifications, but it's likely to be more beneficial to the Ada community. **************************************************************** From: Matthew Heaney Sent: Wednesday, October 9, 2002 12:32 PM > I am saying that trivial reusable components (a simple linked > list) do not > make an effective case for reuse. If that's all we come up > with, I would > say the container effort is a waste of time. How do you feel about the containers in the C++ STL? I searched for your posts on CLA, containing the phrase "STL". Here's a couple of things I found: (start of message) From: dewar@cs.nyu.edu (Robert Dewar) Subject: Re: some questions re. Ada/GNAT from a C++/GCC user Date: 1996/03/27 Message-ID: #1/1 references: organization: Courant Institute of Mathematical Sciences newsgroups: comp.lang.ada,comp.lang.c++ Bill asked: The Ada Programming FAQ pooh-poohs STL, but I like it. (Yes, I know I could write my own versions of what I need, but a bunch of Ada programmers shouldn't need to be told that that's not the ideal solution.) This FAQ also says the Booch components library is coming Those opinions do not represent the opinions of all in the Ada community by any means. The STL is a remarkable piece of work in my opinion. There is some very nice work going on at Rensaleer rewriting STL in Ada. (end message) (start of message) From: dewar@cs.nyu.edu (Robert Dewar) Subject: Re: Ada95 Data Structures Date: 1995/04/20 Message-ID: #1/1 references: <3n6ep4$nac@gopher.cs.uofs.edu> organization: Courant Institute of Mathematical Sciences newsgroups: comp.lang.ada That reminds me, has anyone taken a look at the proposed STL (standard template library) for C++ with an eye to implementing a similar library for Ada 95? (end of message) These posts are admittedly old, but at the time you seemed to express interest in an Ada95 version of the STL. My own efforts have been directed towards such a conversion. http://home.earthlink.net/~matthewjheaney/charles/index.html Exactly like the STL, the Charles container library contains: vectors doubly-linked lists singly-linked lists deques hashed sets hashed multisets sorted sets sorted multisets hashed maps hashed multimaps sorted maps sorted multimaps You are uninterested in "only lists." Are you interested in standardizing any of the other containers listed above? **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 12:39 PM > The Ada Programming FAQ pooh-poohs STL I don't know what FAQ you are talking about, but it has zero official status, and no one I know "pooh-poohs" STL. **************************************************************** From: Matthew Heaney Sent: Wednesday, October 9, 2002 12:50 PM We agree that what the Ada programming FAQ says about the C++ STL is irrelevant. I was soliciting your opinion about the STL, in order to get you to identify the set of containers that would make a standardization effort worthwhile. **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 1:15 PM I don't think I should be in the business of "identifying" the set of containers. I want real Ada programmers to do the job and I want them to "have identified" the set by actually using components that are useful. As Pascal says, the most we should do here is to clean up and put a stamp of approval on existing widely used components that have proved useful in practice. Ada has been around too long to justify the mode of language lawyers and enthusiasts sitting around and telling people what they need :-) **************************************************************** From: Robert A Duff Sent: Wednesday, October 9, 2002 2:43 PM > I don't think I should be in the business of "identifying" the set of > containers. I want real Ada programmers to do the job and I want them > to "have identified" the set by actually using components that are > useful. I partly agree with Robert here. But I think we should not restrict ourselves to listening only to Ada programmers. The Ada world is too insular, IMHO, and we do have something to learn from the larger programming community. In particular, the fact that STL is useful and widely used in the C++ community is good evidence that an Ada binding (or "reimplementation", if you prefer) is a good candidate for Ada standardization. I certainly agree with Robert that the first step is to have a working (portable) implementation. The "Charles" library does not come "out of the blue" -- its spec is directly patterned on the C++ STL. That's good. > As Pascal says, the most we should do here is to clean up and put a stamp > of approval on existing widely used components that have proved useful > in practice. > > Ada has been around too long to justify the mode of language lawyers and > enthusiasts sitting around and telling people what they need :-) Agreed, but when I hear C++ programmers saying they like Ada, except it lacks anything like the STL, and I hear Java programmers saying they like Ada except Java has predefined hash tables and similar stuff, I think we Ada folks should take notice. Ada is not a dead or dying language, so we ought not to serve only existing Ada zealots. We ought to pay attention when outsiders say, "Ada sounds interesting, but..." **************************************************************** From: Jeffrey Carter Sent: Wednesday, October 9, 2002 1:58 PM Robert Dewar wrote: > I was complaining that as far as I can see the packages being proposed here > are out of the blue and have *no* track record of usage. Yes, of course > if we choose some widely used components it is reasonable to clean them > up wrt naming etc. Note incidentally that in this case, it is very important > to clear IPR's properly with the author. The fact that something that is > widely used looks like it has appropriate licensing is legally meaningless. > In general if you download something free from the web you have no idea > what the IPR situation is for serious use. The components I am proposing are the data-structure components from the PragmAda Reusable Components with some things given different names (based on advice from Randy Brukardt). The PragmARCs have existed for many years, first in Ada 83 and now in Ada 95. They have been extensively tested through use on a number of real world projects. The discussion of each component refers to the corresponding PragmARC as the reference implementation. It would be great if the ARG would review existing libraries and choose one for standardization. That was what I was hoping would happen as a result of my original message that started this AI. Tell me that the PragmARCs are it as long as I make certain changes "wrt naming etc." and write the boring documentation and I'll be very happy. However, Randy assures me that such a thing will not happen, so I am formalizing my proposal that the PragmARCs be used as the basis for standard components. **************************************************************** From: Michael Feldman Sent: Wednesday, October 9, 2002 5:04 PM [said Robert Duff] > The "Charles" library does not come "out of the blue" -- its spec is > directly patterned on the C++ STL. That's good. It's useful to recall that the original Rensselaer generic component library that matured into the STL (when one of the RPI developers moved to HP) was written in Ada 83. Having an Ada 0x "STL" would be welcome and is, IMHO, long overdue. Coming full circle, so to speak. > > > As Pascal says, the most we should do here is to clean up and put a stamp > > of approval on existing widely used components that have proved useful > > in practice. > > > > Ada has been around too long to justify the mode of language lawyers and > > enthusiasts sitting around and telling people what they need :-) > > Agreed, but when I hear C++ programmers saying they like Ada, except it > lacks anything like the STL, and I hear Java programmers saying they > like Ada except Java has predefined hash tables and similar stuff, > I think we Ada folks should take notice. > I agree. Industrial-strength languages are pretty much all alike these days. Arguments over features and syntactic stuff are at the margin. It's the libraries that really sell Java, and it's the libraries that will contribute strongly to selling Ada. I've lurked on this list for a long time; having contributed $0.02 to this thread, which is looking rather Team-Ada-like, I'll go back to lurking.:-) **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 9:22 PM <> Well of course standard Java does not have hash tables, since standard Java does not exist. If you are talking about the de facto situation, then there are lots of useful Ada libraries around, so I find that argument bogus. **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 9:25 PM <> Just to be absolutely clear, I am all in favor of someone implementing STL or something like it in Ada. But I think the horse goes before the cart. In this case the horse is the process of creating and testing such a package and getting Ada programmers to try it out. The cart is standardization which follows only if the horse is healthy enough to pull the cart! **************************************************************** From: Robert A Duff Sent: Thursday, October 10, 2002 8:32 PM > Well of course standard Java does not have hash tables, since standard > Java does not exist. Of course Java is a standard. I know you don't like to use the term for "de facto" standards, but everybody else does. And the OED approves. (I have the two-volume version, which comes with a magnifying glass because the print is so fine -- my eyes are now bugging out of my head from reading 10 columns about "standard". ;-) ) I also understand that there is not perfect uniformity across all implementations of Java. But it still seems fair to call Java, as defined by the books from Sun, a "standard". Anyway, if the complaint of the Java programmer is "Java comes with hash tables and Ada doesn't", the response, "Yeah, but Java isn't standard" sounds pretty silly. ;-) > If you are talking about the de facto situation, then there are lots of > useful Ada libraries around, so I find that argument bogus. The hash tables are described in the standard Java bookset from Sun. They come packaged with all implementations of Java. That's a bit different from the situation with Ada, where I might find something by rummaging around on the net. On the other hand (to undermine my own argument in favor of standardizing container libraries), container libraries can be useful without being standardized (however you like to define the term), simply by being available and implemented. This is not true of many other things the ARG is working on. For example, the AI-287 I sent out recently about allowing aggregates of limited types requires the involvement of compiler writers, which pretty much means it has to go through the ARG. Container libraries are different, because their implementation can be completely portable across compilers. **************************************************************** From: Robert Dewar Sent: Thursday, October 10, 2002 5:51 PM <> But de facto standards (like windows) are in fact not at all like "real" standards, so it is worth NOT deliberately getting these terms mixed up. In particular, when something is under control of a single manufacturer, who sues people who do not do what they want, then you can get random changes, and most certainly you do not get the same kind of careful scrutiny. The libraries of Java are very poorly thought out from a standardization point of view, but they are fine as a miscellaneous pot of stuff. <> In fact if you read what I am saying carefully, you will see that I am in fact arguing *FOR* a "de facto standard". If someone can produce a set of container packages that are good enough to become a de facto standard, that is just what we want! **************************************************************** From: Nick Roberts Sent: Thursday, October 10, 2002 9:59 PM >Just to be absolutely clear, I am all in favor of someone >implementing STL or something like it in Ada. >... It is interesting to note the amount of argument this issue (of adding a container (sub-)library of packages, to the Ada standard in the next revision) has generated, just in this normally quiet mailing list in the last couple of weeks. A similar phenomenon has occurred when similar discussions have taken place on comp.lang.ada. It is fairly clear that a great many designs have emerged, all with the same basic thrust, but all with various subtle but significant differences. There are a number of basic design problems, the solutions to which are not clear cut (list provided on request). And inevitably different people have different ideas (philosophies, even) as to where to set the balance of compromises. If any one particular library, of the selection that I am aware of---representing one specific set of compromises---were to be chosen to be enshrined in the Ada standard, I have a nasty feeling it would suffer the indignity of being used only by a relatively small percentage of industrial users, most of whom would choose different libraries because: (a) those libraries fitted their specific requirements more closely; and (b) there would simply be no great counter-advantage to using the standard library. So, although I'm not actually rejecting the arguments for the inclusion of containers in the next revision, I do get the feeling that it may be a bit premature to think that it can be done successfully by 2005 (is this not the putative date?). Ada has been in existence and use longer than C++, and a lot longer than Java. It remains to be seen how long-lived these languages will be; and if they are not, the question of the attractiveness of their libraries will not be an issue. I think we are all assuming, in this mailing list, that Ada will be a long-lived language (in terms of decades, at least), and so I would suggest that we can afford to take a long-term attitude to such an important issue. I am sure the next Ada revision is not going to be the last one. I believe it will take time for the 'right' solution to be developed. It may turn out to be something very complex, which pleases everybody by catering for everybody's requirements explicitly, but which works because it has been well designed, well tested, and well documented. It may turn out to be an elegantly simple design that succeeds because it is able to take advantage of yet-unforseen features added to Ada in future revisions. It may turn out, in time, to be abundantly obvious that no such facility should be made part of the Ada standard. I do not know. But I do believe we should be very wary of plumping for a 'groping-in-the-dark' solution, however tempting. There isn't a _pressing_ need for containers in the Ada standard. My nose- radar, which is usually quite reliable when I use it (not often enough), is currently telling me that, with respect, the ARG needs to (be allowed to) concentrate on smaller, more urgent matters. (But I stand to be corrected, as ever ;-) **************************************************************** From: Robert A Duff Sent: Thursday, October 10, 2002 10:49 AM > It is interesting to note the amount of argument this issue (of > adding a container (sub-)library of packages, to the Ada standard > in the next revision) has generated, just in this normally quiet > mailing list in the last couple of weeks. Well, there's a three-day ARG meeting starting tomorrow, which could explain why we're discussing this and other things. > Ada has been in existence and use longer than C++, and a lot > longer than Java. It remains to be seen how long-lived these > languages will be; and if they are not, the question of the > attractiveness of their libraries will not be an issue. Fortran and COBOL are alive and well. ;-) **************************************************************** From: Robert Dewar Sent: Thursday, October 10, 2002 5:51 PM >>Fortran and COBOL are alive and well. ;-) And neither comes with large standardized libraries. **************************************************************** From: Florian Weimer Sent: Sunday, October 20, 2002 2:35 PM > In particular, the fact that STL is useful and widely used in the C++ > community Is it? Most C++ programmers do not grok generic programming and use the STL only in a very limited way, I guess. For them, the STL is way too complex. Most cross-platform projects still seem to have a "no templates" rule, be it implicit or explicit (see http://www.mozilla.org/hacking/portable-cpp.html). **************************************************************** From: Matthew Heaney Sent: Sunday, October 20, 2002 3:17 PM > Is it? Yes. The STL vastly simplifies using the language. In particular, for beginners it removes the entry barrier to a powerful, but admittedly complex, language. I don't know what I'd do without or . They are ubiquitous in even trivial C++ programs. >Most C++ programmers do not grok generic programming and use > the STL only in a very limited way, I guess. The term "generic programming" describes more than just the STL. It's a powerful feature with which you can effectively extend the language. Classic texts like Barton & Nackman and more recently Alexandrescu illustrate what is possible using the template facility, but this style of programming is relatively advanced. (I certainly don't use it -- but ask me again in another year). Yes, "generic programming" in C++ is complex, but that doesn't gainsay the STL, which is a very useful addition to the language. > For them, the STL is way too complex. This is a specious argument. In fact, just the opposite is true: the language would be too complex *without* the STL. Anyone using C++ but not also using the STL is just making life more difficult for himself. The putative complexity of the STL is really a consequence of library design decisions. There is often a tension between simplicity and generality. In order to make the STL as flexible and general as possible, it means some things become more complex. It's a tradeoff (and I think Stepanov made the right choice). If the "complexity" offends you, then just wrap the STL container in some simpler abstraction and be done with it. You can always make a flexible but complex abstraction less flexible but simple, but the opposite is not true. Tucker and I have debated this issue on another list. > Most cross-platform projects still seem to have a "no templates" rule, > be it implicit or explicit (see > http://www.mozilla.org/hacking/portable-cpp.html). Most of the "problems" compiling the STL have to do with overload resolution rules when using template member functions. The problems usually have a trivial work-around. If you stay away from the dark corners of the language (advice you should heed in Ada95 too...), then you should be OK. **************************************************************** From: Robert Dewar Sent: Sunday, October 20, 2002 8:22 PM This might be true, but has nothing to do with answering Bob's question of whether the STL was widely used. Instead you come back with a technical value statement. If this translated directly into wide use, then presumably Ada itself would be much more widely used. My experience with C++ use in the field, particular in the world of embedded and real time programming, is that Bob is right in his assessment of limited use of the STL. **************************************************************** From: Arnaud Charlet Sent: Monday, October 21, 2002 2:03 PM > Is it? Most C++ programmers do not grok generic programming and use > the STL only in a very limited way, I guess. For them, the STL is way > too complex. That's probably not the best place to discuss C++ programmers usage, but yes, most C++ programmers nowadays consider the STL as simply part of the language and do not hesitate using it. **************************************************************** From: Michael Yoder Sent: Sunday, October 20, 2002 6:46 PM Matthew Heaney wrote: >The putative complexity of the STL is really a consequence of library design >decisions. There is often a tension between simplicity and generality. In >order to make the STL as flexible and general as possible, it means some >things become more complex. It's a tradeoff (and I think Stepanov made the >right choice). > >If the "complexity" offends you, then just wrap the STL container in some >simpler abstraction and be done with it. You can always make a flexible but >complex abstraction less flexible but simple, but the opposite is not true. This isn't so if you use child library units appropriately: put the simple interface in the parent unit and put the extras in child units. This is especially useful when the extras break important properties that hold when the parent unit is used alone: the queue, stack, and deque structures are a good example. Any of these could have an operation to replace an arbitrary element, or one to delete at an arbitrary position, or one to insert an element after an arbitrary position. These might be appropriate for "sophisticated" use (which means in practice a spectrum from genuine sophistication to sneakiness). But such uses destroy the ordering properties which otherwise hold. If it's impossible to get an effect equivalent to child units of generics using templates, or is only possible if you stand on your ear and spit nickels, then I'd agree that the choice was correct. But we can do better with Ada95 generics. **************************************************************** From: Jeffrey Carter Sent: Wednesday, October 16, 2002 8:28 PM !wording A.X.5 Unprotected Bounded Queue Handling The language-defined package Ada.Data_Structures.Queue_Bounded_Unprotected provides a generic package each of whose instances yields a limited private type Handle and a set of operations. An object of a particular Queue_Bounded_Unprotected Handle type represents a queue whose size can vary conceptually between 0 and a maximum size established at the declaration of the object. Static Semantics The library package Ada.Data_Structures.Queue_Bounded_Unprotected has the following declaration: generic -- Ada.Data_Structures.Queue_Bounded_Unprotected type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Data_Structures.Queue_Bounded_Unprotected is pragma Preelaborate; type Handle (Max_Size : Positive) is limited private; procedure Clear (Queue : in out Handle); procedure Assign (To : out Handle; From : in Handle); procedure Put (Into : in out Handle; Item : in Element); procedure Get (From : in out Handle; Item : out Element); function Is_Full (Queue : Handle) return Boolean; function Is_Empty (Queue : Handle) return Boolean; function Length (Queue : Handle) return Natural; function Peek (Queue : Handle) return Element; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Handle; Context : in out Context_Data); private -- Ada.Data_Structures.Queue_Bounded_Unprotected -- not specified by the language end Ada.Data_Structures.Queue_Bounded_Unprotected; A queue is an ordered collection of Elements. Elements are added to the tail of a queue and removed from the head of the queue. A queue is a first in, first out (FIFO) data structure. An object of type Handle is initially empty. procedure Clear (Queue : in out Handle); Clear makes Queue empty. Postcondition: Is_Empty (Queue) Length (Queue) = 0 procedure Assign (To : out Handle; From : in Handle); If To and From are the same queue, Assign has no effect. If To.Max_Size < Length (From), Ada.Data_Structures.Too_Short_Error is propagated. Otherwise, To is cleared and made into a copy of From. Precondition: To.Max_Size >= Length (From) Postcondition: Length (To) = Length (From) procedure Put (Into : in out Handle; Item : in Element); If Into is full, Ada.Data_Structures.Full_Structure_Error is propagated. Otherwise, Put adds Item to the tail of Into. Precondition: not Is_Full (Into) Postcondition: not Is_Empty (Into) Length (Into) > 0 procedure Get (From : in out Handle; Item : out Element); If From is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, the Element at the head of From is removed from From and put in Item. Precondition: not Is_Empty (From) Postcondition: not Is_Full (From) function Is_Full (Queue : Handle) return Boolean; If_Full returns True if Queue is full and False otherwise. function Is_Empty (Queue : Handle) return Boolean; Is_Empty returns True if Queue is empty and False otherwise. function Length (Queue : Handle) return Natural; Length returns the number of Elements stored in Queue function Peek (Queue : Handle) return Element; If Queue is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, Peek returns the Element at the head of Queue without altering Queue. Precondition: not Is_Empty (Queue) generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Handle; Context : in out Context_Data); An instance of this procedure applies Action to each Element stored in Over in turn, from head to tail. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a queue while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. AARM Note This package could be implemented using an instantiation of Ada.Data_Structures.List_Bounded_Unprotected. !discussion The reference implementation of Ada.Data_Structures.Queue_Bounded_Unprotected is PragmARC.Queue_Bounded_Unprotected; it differs in its name and the names of exceptions. Type Handle is a wrapper around a List_Bounded_Unprotected Handle in the reference implementation. !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 1,000 or fewer integers, one per line, read all of the values in Input and store them in a bag. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Queue is new Ada.Data_Structures.Queue_Bounded_Unprotected (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_Queue.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; Queue : Integer_Queue.Handle (Max_Size => 1000); use Integer_Queue; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Put (Item => I, Into => Queue); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Length (Queue) ) ); Put_All (Over => Queue, Context => Dummy); end; **************************************************************** From: Michael Yoder Sent: Thursday, October 17, 2002 8:11 PM The notions of "full" and "empty" should be defined either explicitly or implicitly. A way to define them implicitly would be to change the statements about what Is_Full and Is_Empty return: Is_Full returns True if the length of Queue is Queue.Max_Size. Is_Empty returns True if the length of Queue is zero. There was a typo, If_Full in place of Is_Full. > !wording > > A.X.5 Unprotected Bounded Queue Handling > > [snip] > It is a bounded error to modify a queue while iterating over it. > Possible consequences are: propagating Constraint_Error, propagating an > exception not visible to the client, not processing some of the Elements > stored in Over, processing some of the Elements twice, and normal > processing. > > [snip] Given 9.10(11), I think you can't make this a bounded error if synchronization isn't required in the package body. If the above was written assuming the iteration and modification are done by the same task, that should be stated explicitly; but I wouldn't consider it worth it here to make a special case out of that. **************************************************************** From: Robert A. Duff Sent: Friday, October 18, 2002 9:34 PM > Given 9.10(11), I think you can't make this a bounded error if > synchronization isn't required in the package body. If the above was > written assuming the iteration and modification are done by the same > task, that should be stated explicitly; but I wouldn't consider it worth > it here to make a special case out of that. I'm somewhat skeptical of "bounded error" rules. You have to expend a lot of verbiage describing the bounds, which adds complexity. I'm not sure about this particular case, but I think I prefer either "raise an exception" or "erroneous". It seems to me that the above-quoted rule is analogous to 3.7.2(4): Erroneous Execution 4 {erroneous execution (cause) [partial]} The execution of a construct is erroneous if the construct has a constituent that is a name denoting a subcomponent that depends on discriminants, and the value of any of these discriminants is changed by this execution between evaluating the name and the last use (within this execution) of the subcomponent denoted by the name. 4.a Ramification: This rule applies to assignment_statements, calls (except when the discriminant-dependent subcomponent is an in parameter passed by copy), indexed_components, and slices. Ada 83 only covered the first two cases. AI83-00585 pointed out the situation with the last two cases. The cases of object_renaming_declarations and generic formal in out objects are handled differently, by disallowing the situation at compile time. I'm not sure why "propagating an exception not visible to the client" should be allowed. Don't allow implementation differences unless there's something to be gained (like efficiency). There is a meta-rule in 1.1.5(8), which says bounded errors can always raise Program_Error: 7 Bounded errors; 8 The language rules define certain kinds of errors that need not be detected either prior to or during run time, but if not detected, the range of possible effects shall be bounded. {bounded error} The errors of this category are called bounded errors. {Program_Error (raised by failure of run-time check)} The possible effects of a given bounded error are specified for each such error, but in any case one possible effect of a bounded error is the raising of the exception Program_Error. Clearly 9.10(11) should override any bounded error rule, if that's what's chosen. **************************************************************** From: Michael F. Yoder Sent: Friday, October 18, 2002 9:51 AM I suggest two modifications. (1) Change Peek and its description as follows: function Peek (Queue : Handle; Position : Positive := 1) return Element; Precondition: Length (Queue) >= Position If the length of Queue is less than Position, Ada.Data_Structures.Too_Short_Error is propagated. Otherwise, Peek returns the Element of Queue at the indicated position without altering Queue. When Length (Queue) is nonzero, position 1 indicates the head of Queue, position Length (Queue) indicates its tail, with all positions ordered by time of insertion. (2) Add a generic Reverse_Iterate with the same formal parameters as Iterate and nearly the same description: for "from head to tail" substitute "from tail to head." Alternatively, remove Iterate, since both generics can be composed using the new version of Peek. Jeffrey Carter wrote: > > function Peek (Queue : Handle) return Element; > > generic -- Iterate > type Context_Data (<>) is limited private; > > with procedure Action (Item : in out Element; > Context : in out Context_Data; > Continue : out Boolean); > procedure Iterate (Over : in out Handle; > Context : in out Context_Data); > An instance of this procedure applies Action to each Element stored in > Over in turn, from head to tail. If Action sets Continue to False, this > procedure returns immediately, without processing any remaining Elements > in Over. Context is passed to Action to represent any context data that > Action needs to access or modify. **************************************************************** From: Michael F. Yoder Sent: Friday, October 18, 2002 1:57 PM Much effort can be saved in the Bounded case (and probably other cases) if queues, stacks, and deques are treated together, by realizing that queues (and likewise stacks) act like deques with two procedures missing. The interface with the 2-argument Peek function is clearly a complete abstraction for queues, since you can interrogate the entire state of the queue. For stacks, substitute these two procedures for Put and Get: procedure Push (Into : in out Handle; Item : in Element); procedure Pop (From : in out Handle; Item : out Element); For deques, substitute these 4: procedure Put_Head (Into : in out Handle; Item : in Element); procedure Get_Head (From : in out Handle; Item : out Element); procedure Put_Tail (Into : in out Handle; Item : in Element); procedure Get_Tail (From : in out Handle; Item : out Element); Also systematically substitute "Stack" (or "Deque") for "Queue" in the subprogram arguments. The description I suggested for the semantics of Peek works for stacks and queues but not deques. A new one is given below. The descriptions for the new operations are as follows. Descriptions for Push, Put_Head, and Put_Tail are nearly copies of that for Put; likewise Pop, Get_Head, and Get_Tail are nearly copies of that for Get. procedure Push (Into : in out Handle; Item : in Element); If Into is full, Ada.Data_Structures.Full_Structure_Error is propagated. Otherwise, Push adds Item to the head of Into. Precondition: not Is_Full (Into) Postcondition: not Is_Empty (Into) Length (Into) > 0 procedure Pop (From : in out Handle; Item : out Element); If From is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, the Element at the head of From is removed from From and put in Item. Precondition: not Is_Empty (From) Postcondition: not Is_Full (From) procedure Put_Head (Into : in out Handle; Item : in Element); If Into is full, Ada.Data_Structures.Full_Structure_Error is propagated. Otherwise, Put_Head adds Item to the head of Into. Precondition: not Is_Full (Into) Postcondition: not Is_Empty (Into) Length (Into) > 0 (Put_Tail acts the same way, with "tail" in place of "head.") procedure Get_Head (From : in out Handle; Item : out Element); If From is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, the Element at the head of From is removed from From and put in Item. Precondition: not Is_Empty (From) Postcondition: not Is_Full (From) (Get_Tail acts the same way, with "tail" in place of "head.") For Peek use this description: function Peek (Deque : Handle; Position: Positive := 1) return Element; If the length of Deque is less than Position, Ada.Data_Structures.Too_Short_Error is propagated. Otherwise, Peek returns the Element at the given position. Positions are numbered sequentially from 1 to Length (Deque), with the head at position 1 and the tail at position Length (Deque). **************************************************************** From: Michael F. Yoder Sent: Friday, October 18, 2002 2:24 PM I withdraw a statement I made to the effect that Iterate can be composed using the 2-argument form of Peek. When I tried the example, I noticed what I had previously missed: that the parameter to the Action procedure is 'in out' rather than 'in', so Iterate is iterating over objects rather than values. This is entirely an efficiency issue, but it could be significant. So Iterate should stay, and Reverse_Iterate should be added. I also think my comment about bounded error vs. erroneousness may be incorrect. Presumably there will be a blanket statement somewhere to the effect that the ..._Unprotected generic packages typically use no synchronization, so simultaneous use of structures declared in instances of such generic packages by different tasks without synchronization can be erroneous. The only possible source of concurrent modification and iteration outside of this is by the Action procedure itself, and it might be desirable to make this a special case after all. Indeed this could go one step further: allow modification of the entity without error by Action when the Continue parameter's final value is False, or an exception is propagated. (It should be stated explicitly that an exception propagated by Action halts the iteration and is propagated from Iterate.) In any case, there's nothing fundamental missing other than the extended Peek. There are many other imaginable operations for modifying the queue (or stack or deque), but they belong in a child package if supplied because they break the ordering properties that the structure otherwise enforces. So I say, make the changes and ship it. :-) **************************************************************** From: Jeffrey Carter Sent: Monday, October 21, 2002 2:17 PM > It should be stated explicitly > that an exception propagated by Action halts the iteration and is > propagated from Iterate. This is a good idea that I will adopt in future updates. I ask that Randy apply this to those parts of the proposal that are already delivered. **************************************************************** From: Michael F. Yoder Sent: Tuesday, October 22, 2002 9:01 AM This has made me realize that it's unspecified what happens if Assign propagates an exception during a Put or Get. With what I would call the most straightforward implementation, the result for Put is that a (possibly malformed) item is added to the queue; and for Get, that the item isn't removed. The latter case can lead to a queue that can't be emptied except by calling Clear. On the other hand, if Get discards the item when an exception is propagated, that eliminates any possibility of recovery. This would imply that Assign must itself include any desired recovery actions for, say, Storage_Error when copying substructures. "Implementation-defined" is a poor choice here: we don't need another gratuitous source of incompatibility. Here's some possibilities: (1) Preserve the property of Put and Get that Put always adds an item to a non-full queue (malformed or not) and Get always removes one from a non-empty queue (whether the removed copy is malformed or not). (2) Add a boolean parameter to Put and Get that lets the programmer choose ahead of time which action is wanted. (3) Put and Get both leave the queue unchanged if an exception is propagated. In this case it is desirable to add a procedure that discards the head of the queue without calling Assign. In fact the procedure to discard the head is desirable in any case: if I'm using Peek and decide to eliminate the item, I can avoid the cost of calling Assign. I think (3) is preferable because the programmer has maximum ability to recover from the situation, and the overhead of the boolean parameters needn't be paid. Protecting against adding malformed objects is less important for stacks and deques (if you don't like it you can immediately remove it) but with a queue, once it's in you can't get rid of it without removing everything else. So, I suggest that queues, stacks, and deques consistently leave the structure unchanged if Assign propagates an exception, and that a Discard procedure be added for queues and stacks, and Discard_Head and Discard_Tail for deques. **************************************************************** From: Martin Dowie Sent: Tuesday, October 29, 2002 1:56 AM > If the length of Queue is less than Position, > Ada.Data_Structures.Too_Short_Error is propagated. Is there really a need for another predefined exception - isn't this just a Constraint_Error by another name? **************************************************************** From: Robert I. Eachus Sent: Tuesday, October 29, 2002 6:29 PM Amen! Worse, there would be a temptation to implement this with: when Constraint_Error => raise Ada.Data_Structures.Too_Short_Error; without insuring that no other exceptions could occur in the area protected by the handler. And in fact, if you think in terms of ACVC tests, I can see test arguments about when the call can raise Constraint_Error. Or an implementor using this package, seeing if the subtypes involved were such that a check for when Constraint_Error | Ada.Data_Structures.Too_Short_Error => ...; is what the program should really do. **************************************************************** From: Michael F. Yoder Sent: Tuesday, October 29, 2002 10:34 PM The exception wasn't my invention, I was making the suggestion be consistent with the rest of the proposal. Ada.Data_Structures was defined to contain Empty_Structure_Error, Full_Structure_Error, and Too_Short_Error (and maybe others). Too_Short_Error was raised by the copy subprogram if the destination couldn't hold the source, and it seemed the most appropriate choice for this situation. I sympathize, but I'd be a bit reluctant to make all of the above map to Constraint_Error. Also, as a point of terminology, exceptions defined in language defined packages other than Standard aren't predefined exceptions! For example, the exceptions in IO_Exceptions aren't predefined: see 11.1(4). **************************************************************** From: Jeffrey Carter Sent: Tuesday, October 25, 2002 8:37 PM !wording A.X.6 Protected Bounded Queue Handling The language-defined package Ada.Data_Structures.Queue_Bounded provides a generic package each of whose instances yields a protected type Handle with a set of operations. An object of a particular Queue_Bounded_Protected Handle type represents a queue whose size can vary conceptually between 0 and a maximum size established at the declaration of the object. The client must also specify the ceiling priority of the protected object at its declaration. Static Semantics The library package Ada.Data_Structures.Queue_Bounded has the following declaration: with System; generic -- Ada.Data_Structures.Queue_Bounded type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Data_Structures.Queue_Bounded is pragma Preelaborate; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Continue : out Boolean); protected type Handle (Max_Size : Positive; Ceiling_Priority : System.Any_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; procedure Put (Item : in Element); procedure Get (Item : out Element); function Is_Full return Boolean; function Is_Empty return Boolean; function Length return Natural; function Peek return Element; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Handle -- not specified by the language end Handle; end Ada.Data_Structures.Queue_Bounded; An object of type Handle represents the same abstraction as an object of type Ada.Data_Structures.Queue_Bounded_Unprotected.Handle, but protected to allow access by multiple tasks. An object of type Handle is initially empty. procedure Clear; Clear makes the queue empty Postcondition: Is_Empty procedure Put (Item : in Element); If the queue is full, Ada.Data_Structures.Full_Structure_Error is propagated. Otherwise, Put adds Item to the tail of the queue. Precondition: not Is_Full Postcondition: not Is_Empty procedure Get (Item : out Element); If the queue is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, the Element at the head of the queue is removed from the queue and put in Item. Precondition: not Is_Empty Postcondition: not Is_Full function Is_Full return Boolean; Is_Full returns True if the queue is full and False otherwise. function Is_Empty return Boolean; Is_Empty returns True if the queue is empty and False otherwise. function Length return Natural; Length returns the number of Elements stored in the queue function Peek return Element; If the queue is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, Peek returns the Element at the head of the queue without altering the queue. Precondition: not Is_Empty procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the queue in turn, from head to tail. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the queue. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the queue. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a queue while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in the queue, processing some of the Elements twice, and normal processing. Implementation Permission An implementation may add additional declarations to the specification of Ada.Data_Structures.Queue_Bounded, including a private part for the package, as needed to complete the private part of protected type Handle. AARM Note This package could be implemented using an instantiation of Ada.Data_Structures.Queue_Bounded_Unprotected. !discussion Protected queues are a common and useful method for asynchronous communications among tasks; therefore, I will present protected versions of the queue components. Protected versions of other components are also possible, but less essential, so they will only be presented if time and resources allow. The naming scheme used here is based on that of the reference implementation. The overhead of using a protected object in a sequential program is small; if the implementation uses the ceiling priority locking scheme, the overhead is zero. It is therefore likely that protected components will be used in both concurrent and sequential systems, simply to reduce the complexity of switching between the two forms of a component. In addition, many people like the Name.Operation syntax of a protected object, as well as the fact that without a use clause one does not need to repeat the instantiation name to use the operations. For these reasons, the shorter name is reserved for protected components, with the longer named used for the unprotected versions. I'm not sure if the Implementation Permission is needed, or if it's already implicit. It seems a failing of the language that a visible declaration is needed for what should be completely private information (that is, the type used in the private part of Handle). This proposal is based on the PragmAda Reusable Components, which are Ada 95. If the proposed subprogram parameters (or equivalent) are adopted in the revision of the language, such a parameter should be used for the Action parameter of Iterate. In that case, it is possible to eliminate the context-data parameters from all iterators. Those parameters exist only to allow supplying local context data to protected iterator procedures. The reference version of Ada.Data_Structures.Queue_Bounded is PragmARC.Queue_Bounded. It differs in its name and the names of exceptions. It contains a visible instantiation of PragmARC.Queue_Bounded_Unprotected named Implementation; the private part of Handle is Queue : Implementation.Handle; !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 1,000 or fewer integers, one per line, read all of the values in Input and store them in a queue. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Queue is new Ada.Data_Structures.Queue_Bounded (Element => Integer); procedure Put (I : in out Integer; Context : in out Integer_Queue.Context_Data'Class; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Integer_Queue.Context_Data; Queue : Integer_Queue.Handle (Max_Size => 1000, Ceiling_Priority => System.Default_Priority); begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Queue.Put (Item => I); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Queue.Length) ); Queue.Iterate (Action => Put'access, Context => Dummy); end; **************************************************************** From: Michael F. Yoder Sent: Saturday, October 26, 2002 8:53 AM I'll reiterate that adding a Peek with position parameter is strongly desirable in order to turn an incomplete abstraction into a complete one. If there are efficiency concerns, here's an alternate way of doing this: keep the old function, and have no default in the new one. function Peek return Element; -- Returns same value as Peek (1) but may be faster function Peek (Position : Positive) return Element; **************************************************************** From: Robert I. Eachus Sent: Saturday, October 26, 2002 3:49 PM I like it, but I have one suggestion. The name of the protected type should be Bounded_Queue. The discussion about interfaces indicates to me that we should worry about having multiple instances of the same name and profile with different modes and semantics. This holds whether or not interfaces ever make it into the language. This is a methodology issue. Is it better to have multiple definitions of Handle for the same Element type with different semantics, or is it better to make users think before they reuse code intended for a stack type on a queue? As far as I am concerned, the answer is obvious. If you want to write, for example, a generic to list all elements of a stack or queue, then the instantiations need to be different in any case, but the generic can use a non-descriptive name for the container, such as Handle or Container. At worst this requires an extra generic parameter. But if a user has several container types with the same Element type floating around, the clarity is well worth it. **************************************************************** From: Jeffrey Carter Sent: Saturday, October 26, 2002 9:31 PM !wording A.X.7 Protected Blocking Bounded Queue Handling The language-defined package Ada.Data_Structures.Queue_Bounded_Blocking provides a generic package each of whose instances yields a protected type Handle with a set of operations. An object of a particular Queue_Bounded_Protected Handle type represents a queue whose size can vary conceptually between 0 and a maximum size established at the declaration of the object. The client must also specify the ceiling priority of the protected object at its declaration. Static Semantics The library package Ada.Data_Structures.Queue_Bounded_Blocking has the following declaration: with System; generic -- Ada.Data_Structures.Queue_Bounded_Blocking type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Data_Structures.Queue_Bounded_Blocking is pragma Preelaborate; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Continue : out Boolean); protected type Handle (Max_Size : Positive; Ceiling_Priority : System.Any_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; entry Put (Item : in Element); entry Get (Item : out Element); function Is_Full return Boolean; function Is_Empty return Boolean; function Length return Natural; function Peek return Element; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Handle -- not specified by the language end Handle; end Ada.Data_Structures.Queue_Bounded; An object of type Handle represents the same abstraction as an object of type Ada.Data_Structures.Queue_Bounded.Handle, but Put and Get are entries and block the caller until the queue becomes non-full and non-empty, respectively. An object of type Handle is initially empty. procedure Clear; Clear makes the queue empty Postcondition: Is_Empty entry Put (Item : in Element); If the queue is full, the caller is blocked until the queue becomes non-full. When the queue is not full, Put adds Item to the tail of the queue. Barrier: not Is_Full Postcondition: not Is_Empty entry Get (Item : out Element); If the queue is empty, the caller is blocked until the queue becomes non-empty. When the queue is not empty, the Element at the head of the queue is removed from the queue and put in Item. Barrier: not Is_Empty Postcondition: not Is_Full function Is_Full return Boolean; Is_Full returns True if the queue is full and False otherwise. function Is_Empty return Boolean; Is_Empty returns True if the queue is empty and False otherwise. function Length return Natural; Length returns the number of Elements stored in the queue function Peek return Element; If the queue is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, Peek returns the Element at the head of the queue without altering the queue. Precondition: not Is_Empty procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the queue in turn, from head to tail. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the queue. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the queue. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a queue while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in the queue, processing some of the Elements twice, and normal processing. Implementation Permissions An implementation may add additional declarations to the specification of Ada.Data_Structures.Queue_Bounded_Blocking, including a private part for the package, as needed to complete the private part of protected type Handle. The barriers of Put and Get must have the same effect as the specified barriers, but do not have to be identical to them. AARM Note This package could be implemented using an instantiation of Ada.Data_Structures.Queue_Bounded_Unprotected. !discussion This package is identical to Ada.Data_Structures.Queue_Bounded except that Put and Get are entries. Blocking queues are often useful in concurrent systems. The reference version of Ada.Data_Structures.Queue_Bounded_Blocking is PragmARC.Queue_Bounded_Blocking. It differs in its name and the names of exceptions. It contains a visible instantiation of PragmARC.Queue_Bounded_Unprotected named Implementation; the private part of Handle is Queue : Implementation.Handle; !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 1,000 or fewer integers, one per line, read all of the values in Input and store them in a queue. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Queue is new Ada.Data_Structures.Queue_Bounded_Blocking (Element => Integer); procedure Put (I : in out Integer; Context : in out Integer_Queue.Context_Data'Class; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Integer_Queue.Context_Data; Queue : Integer_Queue.Handle (Max_Size => 1000, Ceiling_Priority => System.Default_Priority); begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Queue.Put (Item => I); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Queue.Length) ); Queue.Iterate (Action => Put'access, Context => Dummy); end; **************************************************************** From: Jeffrey Carter Sent: Wednesday, October 30, 2002 2:04 PM !wording A.X.8 Unprotected Unbounded Queue Handling The language-defined package Ada.Data_Structures.Queue_Unbounded_Unprotected provides a generic package each of whose instances yields a limited private type Handle and a set of operations. An object of a particular Queue_Unbounded_Unprotected Handle type represents a queue whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Data_Structures.Queue_Unbounded_Unprotected has the following declaration: generic -- Ada.Data_Structures.Queue_Unbounded_Unprotected type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Data_Structures.Queue_Unbounded_Unprotected is pragma Preelaborate; type Handle (Max_Size : Positive) is limited private; procedure Clear (Queue : in out Handle); procedure Assign (To : out Handle; From : in Handle); procedure Put (Into : in out Handle; Item : in Element); procedure Get (From : in out Handle; Item : out Element); function Is_Empty (Queue : Handle) return Boolean; function Length (Queue : Handle) return Natural; function Peek (Queue : Handle) return Element; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Handle; Context : in out Context_Data); private -- Ada.Data_Structures.Queue_Unbounded_Unprotected -- not specified by the language end Ada.Data_Structures.Queue_Unbounded_Unprotected; An object of type Handle is initially empty. procedure Clear (Queue : in out Handle); Clear makes Queue empty. Postcondition: Is_Empty (Queue) Length (Queue) = 0 procedure Assign (To : out Handle; From : in Handle); If To and From are the same queue, Assign has no effect. Otherwise, To is cleared and made into a copy of From. If, after clearing To, there is insufficient storage for copies of all the Elements in From, Storage_Exhaustion_Error is propagated. Postcondition: Length (To) = Length (From) procedure Put (Into : in out Handle; Item : in Element); If there is insufficient storage available to store Item in Into, Storage_Exhaustion_Error is propagated. Otherwise, Put adds Item to the tail of Into. Postcondition: not Is_Empty (Into) Length (Into) > 0 procedure Get (From : in out Handle; Item : out Element); If From is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, the Element at the head of From is removed from From and put in Item. Precondition: not Is_Empty (From) Postcondition: not Is_Full (From) function Is_Empty (Queue : Handle) return Boolean; Is_Empty returns True if Queue is empty and False otherwise. function Length (Queue : Handle) return Natural; Length returns the number of Elements stored in Queue. function Peek (Queue : Handle) return Element; If Queue is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, Peek returns the Element at the head of Queue without altering Queue. Precondition: not Is_Empty (Queue) generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Handle; Context : in out Context_Data); An instance of this procedure applies Action to each Element stored in Over in turn, from head to tail. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the queue. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a queue while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. AARM Note This package could be implemented using an instantiation of Ada.Data_Structures.List_Unbounded_Unprotected. !discussion The reference implementation of Ada.Data_Structures.Queue_Unbounded_Unprotected is PragmARC.Queue_Unbounded_Unprotected; it differs in its name and the names of exceptions. Type Handle is a wrapper around a List_Unbounded_Unprotected Handle in the reference implementation. !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a queue. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Queue is new Ada.Data_Structures.Queue_Unbounded_Unprotected (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_Queue.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; Queue : Integer_Queue.Handle; use Integer_Queue; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Put (Item => I, Into => Queue); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Length (Queue) ) ); Put_All (Over => Queue, Context => Dummy); end; **************************************************************** From: Jeffrey Carter Sent: Wednesday, October 30, 2002 2:14 PM !wording A.X.9 Protected Unbounded Queue Handling The language-defined package Ada.Data_Structures.Queue_Unbounded provides a generic package each of whose instances yields a protected type Handle with a set of operations. An object of a particular Queue_Unbounded_Protected Handle type represents a queue whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Data_Structures.Queue_Unbounded has the following declaration: with System; generic -- Ada.Data_Structures.Queue_Unbounded type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Data_Structures.Queue_Unbounded is pragma Preelaborate; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Continue : out Boolean); protected type Handle (Ceiling_Priority : System.Any_Priority := System.Default_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; procedure Put (Item : in Element); procedure Get (Item : out Element); function Is_Empty return Boolean; function Length return Natural; function Peek return Element; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Handle -- not specified by the language end Handle; end Ada.Data_Structures.Queue_Unbounded; An object of type Handle represents the same abstraction as an object of type Ada.Data_Structures.Queue_Unbounded_Unprotected.Handle, but protected to allow access by multiple tasks. An object of type Handle is initially empty. procedure Clear; Clear makes the queue empty Postcondition: Is_Empty procedure Put (Item : in Element); If there is insufficient storage available to store Item in the queue, Storage_Exhaustion_Error is propagated. Otherwise, Put adds Item to the tail of the queue. Postcondition: not Is_Empty procedure Get (Item : out Element); If the queue is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, the Element at the head of the queue is removed from the queue and put in Item. Precondition: not Is_Empty Postcondition: not Is_Full function Is_Empty return Boolean; Is_Empty returns True if the queue is empty and False otherwise. function Length return Natural; Length returns the number of Elements stored in the queue. function Peek return Element; If the queue is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, Peek returns the Element at the head of the queue without altering the queue. Precondition: not Is_Empty procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the queue in turn, from head to tail. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the queue. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the queue. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a queue while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in the queue, processing some of the Elements twice, and normal processing. Implementation Permission An implementation may add additional declarations to the specification of Ada.Data_Structures.Queue_Unbounded, including a private part for the package, as needed to complete the private part of protected type Handle. AARM Note This package could be implemented using an instantiation of Ada.Data_Structures.Queue_Unbounded_Unprotected. !discussion The reference version of Ada.Data_Structures.Queue_Unbounded is PragmARC.Queue_Unbounded. It differs in its name and the names of exceptions. It contains a visible instantiation of PragmARC.Queue_Unbounded_Unprotected named Implementation; the private part of Handle is Queue : Implementation.Handle; !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a queue. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Queue is new Ada.Data_Structures.Queue_Unbounded (Element => Integer); procedure Put (I : in out Integer; Context : in out Integer_Queue.Context_Data'Class; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Integer_Queue.Context_Data; Queue : Integer_Queue.Handle; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Queue.Put (Item => I); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Queue.Length) ); Queue.Iterate (Action => Put'access, Context => Dummy); end; **************************************************************** From: Jeffrey Carter Sent: Wednesday, October 30, 2002 2:24 PM !wording A.X.10 Protected Blocking Unbounded Queue Handling The language-defined package Ada.Data_Structures.Queue_Unbounded_Blocking provides a generic package each of whose instances yields a protected type Handle with a set of operations. An object of a particular Queue_Unbounded_Protected Handle type represents a queue whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Data_Structures.Queue_Unbounded_Blocking has the following declaration: with System; generic -- Ada.Data_Structures.Queue_Unbounded_Blocking type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Data_Structures.Queue_Unbounded_Blocking is pragma Preelaborate; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Continue : out Boolean); protected type Handle (Ceiling_Priority : System.Any_Priority := System.Default_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; procedure Put (Item : in Element); entry Get (Item : out Element); function Is_Empty return Boolean; function Length return Natural; function Peek return Element; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Handle -- not specified by the language end Handle; end Ada.Data_Structures.Queue_Unbounded; An object of type Handle represents the same abstraction as an object of type Ada.Data_Structures.Queue_Bounded_Blocking.Handle, except it is unbounded. An object of type Handle is initially empty. procedure Clear; Clear makes the queue empty Postcondition: Is_Empty procedure Put (Item : in Element); If there is insufficient storage available to store Item in the queue, Storage_Exhaustion_Error is propagated. Otherwise, Put adds Item to the tail of the queue. Postcondition: not Is_Empty entry Get (Item : out Element); If the queue is empty, the caller is blocked until the queue becomes non-empty. When the queue is not empty, the Element at the head of the queue is removed from the queue and put in Item. Barrier: not Is_Empty Postcondition: not Is_Full function Is_Empty return Boolean; Is_Empty returns True if the queue is empty and False otherwise. function Length return Natural; Length returns the number of Elements stored in the queue function Peek return Element; If the queue is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, Peek returns the Element at the head of the queue without altering the queue. Precondition: not Is_Empty procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the queue in turn, from head to tail. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the queue. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the queue. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a queue while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in the queue, processing some of the Elements twice, and normal processing. Implementation Permissions An implementation may add additional declarations to the specification of Ada.Data_Structures.Queue_Unbounded_Blocking, including a private part for the package, as needed to complete the private part of protected type Handle. The barrier of Get must have the same effect as the specified barrier, but does not have to be identical to it. AARM Note This package could be implemented using an instantiation of Ada.Data_Structures.Queue_Unbounded_Unprotected. !discussion The reference version of Ada.Data_Structures.Queue_Unbounded_Blocking is PragmARC.Queue_Unbounded_Blocking. It differs in its name and the names of exceptions. It contains a visible instantiation of PragmARC.Queue_Unbounded_Unprotected named Implementation; the private part of Handle is Queue : Implementation.Handle; !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a queue. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Queue is new Ada.Data_Structures.Queue_Unbounded_Blocking (Element => Integer); procedure Put (I : in out Integer; Context : in out Integer_Queue.Context_Data'Class; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Integer_Queue.Context_Data; Queue : Integer_Queue.Handle; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Queue.Put (Item => I); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Queue.Length) ); Queue.Iterate (Action => Put'access, Context => Dummy); end; **************************************************************** From: Jeffrey Carter Sent: Friday, November 1, 2002 5:27 PM !wording A.X.11 Discrete Set Handling The language-defined package Ada.Data_Structures.Set_Discrete provides a generic package each of whose instances yields a limited private type Handle and a set of operations. An object of a particular Set_Discrete Handle type represents a set over a universe defined by the generic discrete formal type Element. Static Semantics The library package Ada.Data_Structures.Set_Discrete has the following declaration: generic -- Ada.Data_Structures.Set_Discrete type Element is (<>); package Ada.Data_Structures.Set_Discrete is pragma Pure; type Handle is private; Empty : constant Handle; Full : constant Handle; function "+" (Left : Handle; Right : Handle) return Handle; function Union (Left : Handle; Right : Handle) return Handle renames "+"; function "+" (Left : Handle; Right : Element) return Handle; function "+" (Left : Element; Right : Handle) return Handle; function Union (Left : Handle; Right : Element) return Handle renames "+"; function Union (Left : Element; Right : Handle) return Handle renames "+"; function "*" (Left : Handle; Right : Handle) return Handle; function Intersection (Left : Handle; Right : Handle) return Handle renames "*"; function "-" (Left : Handle; Right : Handle) return Handle; function Difference (Left : Handle; Right : Handle) return Handle renames "-"; function "-" (Left : Handle; Right : Element) return Handle; function Difference (Left : Handle; Right : Element) return Handle renames "-"; function "/" (Left : Handle; Right : Handle) return Handle; function Symmetric_Differece (Left : Handle; Right : Handle) return Handle renames "/"; function "<=" (Left : Handle; Right : Handle) return Boolean; function Subset (Left : Handle; Right : Handle) return Boolean renames "<="; function "<" (Left : Handle; Right : Handle) return Boolean; function Proper_Subset (Left : Handle; Right : Handle) return Boolean renames "<"; function ">=" (Left : Handle; Right : Handle) return Boolean; function Superset (Left : Handle; Right : Handle) return Boolean renames ">="; function ">" (Left : Handle; Right : Handle) return Boolean; function Proper_Superset (Left : Handle; Right : Handle) return Boolean renames ">"; function Member (Item : Element; Set : Handle) return Boolean; type Member_List is array (Positive range <>) of Element; function Make_Set (List : Member_List) return Handle; function Size (Set : Handle) return Natural; private -- Ada.Data_Structures.Set_Discrete -- not defined by the language end Ada.Data_Structures.Set_Discrete; An object of type Handle is intially empty. The constant Handle Empty represents a set with no elements. The constant Handle Full represents a set with all possible elements. function "+" (Left : Handle; Right : Handle) return Handle; function Union (Left : Handle; Right : Handle) return Handle renames "+"; "+" (Union) returns the union of its two parameters. function "+" (Left : Handle; Right : Element) return Handle; function "+" (Left : Element; Right : Handle) return Handle; function Union (Left : Handle; Right : Element) return Handle renames "+"; function Union (Left : Element; Right : Handle) return Handle renames "+"; These versions of "+" (Union) return Left + Make_Set ( (1 => Right) ) and Make_Set ( (1 => Left) ) + Right, respectively. function "*" (Left : Handle; Right : Handle) return Handle; function Intersection (Left : Handle; Right : Handle) return Handle renames "*"; "*" (Intersection) returns the intersection of its two parameters. function "-" (Left : Handle; Right : Handle) return Handle; function Difference (Left : Handle; Right : Handle) return Handle renames "-"; "-" (Difference) returns the difference of its two parameters. function "-" (Left : Handle; Right : Element) return Handle; function Difference (Left : Handle; Right : Element) return Handle renames "-"; This version of "-" (Difference) returns Left - Make_Set ( (1 => Right) ). function "/" (Left : Handle; Right : Handle) return Handle; function Symmetric_Differece (Left : Handle; Right : Handle) return Handle renames "/"; "/" (Symmetric_Difference) returns the symmetric differece of its two parameters. function "<=" (Left : Handle; Right : Handle) return Boolean; function Subset (Left : Handle; Right : Handle) return Boolean renames "<="; "<=" (Subset) returns True if Left is a subset of Right. Otherwise it returns False. function "<" (Left : Handle; Right : Handle) return Boolean; function Proper_Subset (Left : Handle; Right : Handle) return Boolean renames "<"; "<" (Proper_Subset) returns True if Left is a proper subset of Right. Otherwise it returns False. function ">=" (Left : Handle; Right : Handle) return Boolean; function Superset (Left : Handle; Right : Handle) return Boolean renames ">="; ">=" (Superset) returns True if Left is a superset of Right. Otherwise it returns False. function ">" (Left : Handle; Right : Handle) return Boolean; function Proper_Superset (Left : Handle; Right : Handle) return Boolean renames ">"; ">" (Proper_Superset) returns True if Left is a proper superset of Right. Otherwise it returns False. function Member (Item : Element; Set : Handle) return Boolean; Member returns True if Item is a Member of Set. Otherwise it returns False. The type Member_List is used by Make_Set to allow an equivalent of a literal of type Handle. function Make_Set (List : Member_List) return Handle; Make_Set returns a Handle such that Member returns True for each Element in List and False for each Element not in List. function Size (Set : Handle) return Natural; Size returns the number of Elements in Set. Implementation Permission The operations defined in terms of Make_Set may be implemented directly, without calling Make_Set or any other operations of the package. AARM Note Handle could be implemented as an array indexed by Element with components of Boolean, wrapped in a record to allow objects to be initialized to Empty. !discussion We would really like to use "in" instead of "Member". The reference implementation of Ada.Data_Structures.Set_Discrete is PragmARC.Set_Discrete. It differs in some of the type and parameter names used. In the reference implementation, Handle is a record containing an array component. The array type has Element as its index and Boolean components, and has 'Component_Size defined as one and pragma Pack applied to it. !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers in the range 1 .. 1_000, one per line, read all of the values in Input and add them to a set. Once the entire file has been read, output the number of unique values read followed by all the unique values, in ascending order, to the standard output. declare subtype Number is Integer range 1 .. 1_000; package Number_Set is new Ada.Data_Structures.Set_Discrete (Element => Number); I : Number; Set : Number_Set.Handle; use type Number_Set.Handle; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Set := Set + I; end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Number_Set.Size (Set) ) ); Print_All : for J in Number loop if Number_Set.Member (J, Set) then Ada.Text_IO.Put_Line (Item => Integer'Image (J) ); end if; end loop Print_All; end; **************************************************************** From: Robert Dewar Sent: Tuesday, November 1, 2002 9:19 PM I must say I *really* disklike the use of Handle in this spec, and Set_Discrete.Handle does not read well either (sounds like a verb acting on a noun). **************************************************************** From: Michael F. Yoder Sent: Saturday, November 2, 2002 12:18 AM I've done a great deal of discrete set manipulation, perhaps more in Pascal than Ada. This package is incomplete for serious applications. The first things needed are choice functions: function First (Set : Handle) return Element; function Last (Set : Handle) return Element; These return the smallest (resp. largest) element of Set if Set isn't empty. If Set is empty, Constraint_Error is raised. It is highly desirable for the sake of efficiency to add something like these as well: procedure Add_Element (Set : in out Handle; Elem : in Element); procedure Remove_Element (Set : in out Handle; Elem : in Element); The procedure Remove_Element, plus First and Last, allows adequately efficient iterators to be constructed in both directions (beware that this method loses to the naive method for large enough sets). You can do better in general if the iterators are added to the package. **************************************************************** From: Pascal Obry Sent: Saturday, November 2, 2002 3:54 AM > The first things needed are choice functions: > > function First (Set : Handle) return Element; > function Last (Set : Handle) return Element; This is of course not really needed in Ada since we have attributes 'First, 'Last, 'Succ and 'Pred. **************************************************************** From: Robert Dewar Sent: Saturday, November 2, 2002 6:42 AM How could such attributes apply to Handle, I do not understand. By the way, I see no reason to imply that sets are kept sorted, since this requires a rather inefficient (from the point of view of average performance behavior) storage structure. **************************************************************** From: Robert Dewar Sent: Saturday, November 2, 2002 6:43 AM <> Perhaps of course Pascal is subtly suggesting user definable attributes, interesting idea :-) Should keep the hobbyists and language maximalists happy for a while :-) Might even be useful! **************************************************************** From: Robert A Duff Sent: Saturday, November 2, 2002 9:40 PM ;-) I think Pascal is neither a hobbyist nor a maximalist, though. I think he just misunderstood the intended semantics of the First and Last functions. **************************************************************** From: Pascal Obry Sent: Saturday, November 2, 2002 7:31 AM You don't need the handle since this is the first and last value for the actual type as provided by client of the package. I really don't see 'First and 'Last has being needed here! Client of the package can always use the 'First and 'Last (and whatever attributes) defined on discrete types. **************************************************************** From: Robert Dewar Sent: Saturday, November 2, 2002 7:43 AM But I thought the idea was to give the first and last of the elements *currently in the set*. **************************************************************** From: Pascal Obry Sent: Saturday, November 2, 2002 8:02 AM Maybe but in that case I think this should be part of an Iterator. **************************************************************** From: Michael F. Yoder Sent: Saturday, November 2, 2002 8:57 AM >This is of course not really needed in Ada since we have attributes 'First, >'Last, 'Succ and 'Pred. You misunderstand. Here's the description I wrote: These return the smallest (resp. largest) element of Set if Set isn't empty. If Set is empty, Constraint_Error is raised. These don't return the first and last elements of the type Element. They return the first and last elements of the argument Set. Thus, if type Element is (W, R, G, B, C, M, Y, K); and conceptually Set = {R, G, B}, then First (Set) = R, Last (Set) = B. **************************************************************** From: Robert Dewar Sent: Saturday, November 2, 2002 9:05 AM And that's what I don't like. Conceptually sets are not ordered. **************************************************************** From: Michael F. Yoder Sent: Saturday, November 2, 2002 11:50 AM True, but these aren't sets over general types. Type Element has known operations; it's OK to take advantage of them, and say the abstraction is not just "set" but specifically "set of ordered type." When dealing with sets in nontrivial ways, you need a choice function. In a pure generic package, the function must be deterministic; there are two obvious possibilities, and it's prudent to provide both. I'd happily name them Choice_That_Just_Happens_To_Coincide_With_Max and ..._Min if that avoided objections along these lines. If not, the problem should be addressed by renaming the package to acknowledge that the abstraction is of sets over a finite fully ordered type and not general sets; but the formal parameter conveys this information without needing further explication. The alternative makes the package unusable unless you can add a child package, or when any implementation that isn't absurd is adequate; if this is to be in the Ada hierarchy, most users won't have that option. **************************************************************** From: Michael F. Yoder Sent: Saturday, November 2, 2002 12:06 PM >This is of course not really needed in Ada since we have attributes 'First, >'Last, 'Succ and 'Pred. > I may have made a poor choice of names here, contributing to confusion. Perhaps Min and Max would be better. In fact, Min could be defined to return Element'Last for the empty set, and Max could return Element'First. This would *sometimes* be preferable to raising an exception. To be honest, in my coding I've generally preferred raising Program_Error in that case, since trying to get an element out of the empty set feels like an offense against all of mathematics. :-) However, these aren't general sets, and the alternate definition has the right mathematical properties; it's like defining the sum over an empty set to be zero, or the product to be unity. **************************************************************** From: Robert A Duff Sent: Saturday, November 2, 2002 12:33 PM > I may have made a poor choice of names here, contributing to confusion. > Perhaps Min and Max would be better. I like the names Min and Max better. > In fact, Min could be defined to return Element'Last for the empty set, > and Max could return Element'First. This would *sometimes* be preferable > to raising an exception. I don't like it. >... To be honest, in my coding I've generally > preferred raising Program_Error in that case, since trying to get an > element out of the empty set feels like an offense against all of > mathematics. :-) Yeah, I agree with that -- stick with C_E. The typical use is something like (leaving out the annoyingly necessary declare/begin/end): while not Is_Empty(Some_Set) loop X: constant Element := Min(Some_Set); grind upon X; Remove X from Some_Set; end loop; or, if you don't want to destroy the set: if not Is_Empty(Some_Set) then X: Element := Min(Some_Set); loop grind upon X; exit when X = Max(Some_Set); X := Next(Some_Set, X); end loop; end if; The C_E catches the bug of forgetting to exit the loop at the right time. Making Min return Element'Last would not simplify the second loop; you still need to distinguish {Element'Last} from {}. Encapsulating the latter in a generic iterator seems like a good idea to me. Using that eliminates the bug of forgetting to move to the next item (or remove the item). >...However, these aren't general sets, and the alternate > definition has the right mathematical properties; it's like defining the > sum over an empty set to be zero, or the product to be unity. I don't agree with that. To me, it's more like defining 0/0 to equal 17. I've heard at least one language designer advocate eliminating *all* run-time errors simply by making sure every operation returns *some* value in all cases. It's possible, but not a good idea. **************************************************************** From: Ted Baker Sent: Saturday, November 2, 2002 3:24 PM Michael's proposal to include a "choice" function (as in Axiom of Choice) makes sense. It does not matter what ordering relation it uses, so long as the choice is deterministic (repeatable with the same result). Even though the set itself does not define and order on its members, the elements are from some domain that the implementation can order. **************************************************************** From: Robert Dewar Sent: Saturday, November 2, 2002 8:20 PM <> Once again, non-deterministic does NOT mean non-repeatable, it simply means that the semantics of the choice are undefined. Sometimes people think of non-deterministic meaning random but this of course is not the case. **************************************************************** From: Robert Dewar Sent: Saturday, November 2, 2002 6:09 PM <> Sorry, I have no idea what you mean by "the function must be deterministic". That seems obviously overspecification for a set package. **************************************************************** From: Michael F. Yoder Sent: Saturday, November 2, 2002 7:56 PM The functions would be pure. Maybe it would be better to say they *should* be deterministic, because I as the user don't want to deal with the ramifications of 10.2.1(18) if they aren't. And it would *perhaps* be overspecification for "a set package", but this is a specific species of set package, and that matters. Levels of abstraction are a choice, and artificial restrictions here are harmful, not helpful. Frege wrote a wonderful essay on abstraction long before electronic computers existed that would be apropos to insert here; one of his points was that abstraction as usually described amounted to intentionally overlooking aspects of entities. That's fine, but if you overlook too much, you're in danger of falling into a well while looking at the stars. It's impossible to provide a generic actual to this package that isn't a discrete type, so the fact that general sets don't have to involve ordered entities is *entirely* irrelevant, except possibly as a matter of presentation. It isn't *only* a set package, it's specifically a set-of-discrete-type package. The essay I refer to can be found in Michael Dummett's book on Frege (I think he only wrote one) in English translation. I wholeheartedly recommend the book as a whole, not just for this piece. **************************************************************** From: Robert Dewar Sent: Saturday, November 2, 2002 8:14 PM I think you are mixing up the implementation with the conceptual semantics. To me the conceptual semantics should be non-deterministic (as in SETL). The fact that the underlying implementation is deterministic is irrelevant. For example, you might do everything with hash tables, and return the item with the smallest hash code, but that would not be part of the spec. It could be part of the spec that the function is pure in the technical sense, so that the result is repeatable (SETL is like that, at least in practice). I think it would be a good idea to look at SETL if you want to produce set abstractions. **************************************************************** From: Michael F. Yoder Sent: Saturday, November 2, 2002 8:38 PM >I think you are mixing up the implementation with the conceptual semantics. No! It's precisely the allowed variability in *semantics* that I don't want to deal with. It's much harder to deal with a choice function if A = B doesn't imply Choice (A) = Choice (B). >To me the conceptual semantics should be non-deterministic (as in SETL). >The fact that the underlying implementation is deterministic is irrelevant. >[snip] That isn't sufficient. Because pure packages can be replicated across partitions, I need also for the result to be the same when executed in any partition. Otherwise, I can introduce a new partition and have my program break. It is just infinitely simpler to specify a choice function or two (since it's so easy) than to say, well, it's not deterministic, but all partitions must arrange to make the same choice. **************************************************************** From: Robert Dewar Sent: Saturday, November 2, 2002 9:15 PM I consider any program that depends on determinism when dealing with sets to be broken but then I have a much higher view of the desired level of abstraction here (once again, SETL represents my starting point for desirable ZF set abstractions). **************************************************************** From: Michael F. Yoder Sent: Saturday, November 2, 2002 9:31 PM That's where we disagree. In this instance, "higher" is synonymous with "wrong." That's the gist of the comment I made about Frege's essay. And I've read the SETL manual, and also taught ZF theory and logic at the graduate level at CIT. **************************************************************** From: Robert Dewar Sent: Sunday, November 3, 2002 5:43 AM Well of course the best way to resolve such disagrements, especially if they get to the stage where one party runs out of technical arguments and simply labels the other "wrong", is to explore both alternatives, and it seems reasonable to me to have packages designed in both directions, though in my opinion neither belongs in the standard (my own view is that very few of these half baked packages belong in the standard anyway -- half baked here refers to the notion that full baking would require real use by real Ada users with a significant track record). I really think the best approach for packages would be to sell them in the market place first. I don't mean literally "sell for money" here, but rather publish them and get them used. If two different large scale projects come to the table and say "that package is really useful, we have been using it for xxx years in yyy projects and think that it would be helpful to have this in the standard", then that's really significant. Note however, that though in my view it is *necessary* to present such evidence of real usage, it is not necessary. Just because packages have been used in significant applications and contexts is not of itself a reason to chuck things into the standard. After all, all the packages in the GNAT.xxx library have been widely used by real Ada users, but very few of them, perhaps none, deserve serious consideration for addition to the standard. P.S. I read the Frege piece, and indeed I disagree. I don't see abstraction as a damaging concept :-) **************************************************************** From: Robert Dewar Sent: Sunday, November 3, 2002 5:45 AM My the way Michael, you did not respond to my technical point that purity and non-determinism (at the abstract level) are not inconsistent. I assume you agree with that point technically, but that you still prefer to keep an ordering notion and deterministic selection (why I am not quite sure, since you have not presented any specific arguments in favor of this alternative except to quote the Frege essay, which is rather general and only by stretching and subjective impression applies to this particular case (to a certain extent the Frege essay says things which everyone can agree with abstractly, but when it comes to applying them to a specific case, that's when specific technical arguments are neeed!) **************************************************************** From: Jean-Pierre Rosen Sent: Monday, November 4, 2002 3:55 AM > True, but these aren't sets over general types. Type Element has known > operations; it's OK to take advantage of them, and say the abstraction > is not just "set" but specifically "set of ordered type." Aren't you confusing "set of ordered type" with "ordered set of type"? **************************************************************** From: Michael F. Yoder Sent: Monday, November 4, 2002 7:31 AM No, "set of ordered type" is the more correct term here, the other could be misleading. The ordering relation used comes with the type, it isn't imposed by the generic. **************************************************************** From: Robert Dewar Sent: Monday, November 4, 2002 8:21 AM I disagree, the issue is whether the set is conceptually ordered or not. Certainly the set cannot be ordered if the type is not ordered, but equally certainly the set can be unordered even though the elements are ordered. What Michael has been arguing for is indeed that the abstraction should be an ordered set abstraction so the name should reflect this. **************************************************************** From: Michael F. Yoder Sent: Monday, November 4, 2002 1:10 PM I'm trying to find a construal of "conceptually ordered" that makes sense here. I think there's likely a confusion between two different terms. If "conceptually unordered" just means that, e.g., {a, b} = {b, a} and so forth, it's not relevant to the discussion unless implementation issues are mixed up with the abstraction. What matters here is plain "ordered" as in having an ordering, and in ZF sets are not unordered but rather multiply ordered (the well-ordering theorem implies this). There seems to be agreement that ZF is the right value domain, which is good. So let's follow through here. It's provable *within ZF* that there's a unique function Min bearing a special relationship to the function "<" of the generic formal. It's provable *within ZF* that there's a unique function Max bearing the same relation to ">". So how does supplying them "break" the ZF abstraction in any way? If it's illegitimate to refer to the generic formal type (why??), wouldn't the constant Full be illegitimate also? **************************************************************** From: Ted Baker Sent: Monday, November 4, 2002 5:17 AM I added the parenthetical "repeatable with the same result" to make it clear how I read Michael's usage of the term "nondeterministic", which we all know means different things to different people. My own preferred usage is precisely as in nondeterministic Turing Machine and NDFA. However, it seems to me most people use the term in a broader sense, to including the outcome being one of a range of values, from which a different value may occur on each execution. I interpreted Michael's comments as following that usage, i.e., that the choice operation be a function of the set, in the mathematical sense. **************************************************************** From: Robert Dewar Sent: Monday, November 4, 2002 3:49 PM Sure but the issue is whether it is a defined function. For example, it is crucial in SETL that the formal semantics is non-deterministic, and any program depending on repeatable results is considered incorrect. Note that we have an analogy in Ada. The task chosen in a sleect statment is chosen non-determinisitically in the base language, but this does not mean non-repeatably. **************************************************************** From: Robert A Duff Sent: Monday, November 4, 2002 5:32 PM It implies that there is no guarantee of repeatability in the language. An implementation can of course *choose* to be repeatable. The analogy with SETL is inapt: SETL sets are sets of arbitrary things. If Ada has *that* kind of set, then the analogy works, and it makes sense to have non-deterministic choice functions. But the package we're talking about is a set of *discrete*, and it's intended to be implemented as a bit map. I claim that in *this* case, the choice function should be well defined, deterministic (and therefore repeatable), based on the natural ordering of the element type. **************************************************************** From: Ted Baker Sent: Monday, November 4, 2002 1:50 PM > I disagree, the issue is whether the set is conceptually ordered or not. > Certainly the set cannot be ordered if the type is not ordered, but > equally certainly the set can be unordered even though the elements > are ordered. > > What Michael has been arguing for is indeed that the abstraction should > be an ordered set abstraction so the name should reflect this. ???? I my readings the term "ordered set" is used to distinguish a normal (unordered) set from a set that imposed an order on its elements. That is, the ordered set {1, 3, 5} would be distinct from the ordered set {3, 1, 5}. I guess you are *not* proposing that. **************************************************************** From: Robert A Duff Sent: Monday, November 4, 2002 7:38 AM > Aren't you confusing "set of ordered type" with "ordered set of type" ? I don't think so. The generic (which I have suggested be called Bit_Map_Sets) takes a generic formal discrete type called Element. Element is an "ordered type" -- that is, it has a natural order independent of any sets of other abstractions. E.g., it might be Character, where 'a' < 'b' even when there are no sets-of-Character around the place. So a set of Element is a "set of ordered type", as Mike said. In other words, the order comes from the natural order of the Element type, not from some property of the set. The Min operation proposed by Mike Yoder simply returns that element of the set that is smallest according to the natural ordering of the Element type (or raises C_E if the set is empty). Makes perfect sense to me. **************************************************************** From: Robert A Duff Sent: Monday, November 4, 2002 9:13 AM > What Michael has been arguing for is indeed that the abstraction should > be an ordered set abstraction so the name should reflect this. It would also be possible to define a "set"-like abstraction that exports *both* ordered and unordered versions of iterating operations. I'm not advocating we do that (for this "Bit_Mapped_Set" thing, as I like to call it); I'm just pointing out that it's not entirely a property of the "set" type itself. **************************************************************** From: Jeffrey Carter Sent: Saturday, November 2, 2002 1:49 PM > I must say I *really* disklike the use of Handle in this spec, and > Set_Discrete.Handle does not read well either (sounds like a verb > acting on a noun). I somehow doubt that clients will name instantiations "Set_Discrete". It has been my experience that no name will be completely satisfactory to everyone. I have chosen to use Handle for everything. If there's a consensus for something else, it's easy enough to change so long as it is not the same as parameter names. **************************************************************** From: Michael F. Yoder Sent: Saturday, November 2, 2002 4:03 PM I had a reaction similar to Robert's, but didn't want to say anything if I was the only one who didn't like it. :-) Appeal to aesthetics may be hopeless, but appeal to precedent isn't. How about Discrete_Set for the type name? It should be at least as acceptable as the already existing Character_Set and Wide_Character_Set. **************************************************************** From: Robert Dewar Sent: Saturday, November 2, 2002 6:53 PM I would think that Ordered_Set would be better, and in fact I see no reason to restrict it to discrete types (I would prefer to provide whatever operations are required). **************************************************************** From: Robert Dewar Sent: Saturday, November 2, 2002 8:18 PM <> I prefer names that reflect the underlyin conceptual entity. Handle sounds like it is far too implementation oriented. **************************************************************** From: Jeffrey Carter Sent: Sunday, November 3, 2002 12:09 PM Perhaps Handle has a technical meaning that I've been able to avoid encountering in over 27 years of professional software development. I use the term to refer to something that allows you to manipulate something without knowing its implementation. By this definition, any private type is a handle. The main use of Handle I have encountered is "file handle", which is usually an integer returned as a result of opening or creating a file. Clearly this integer is only a pointer to some hidden information that allows a file to be manipulated. Thus, this use is consistent with mine. **************************************************************** From: Robert Dewar Sent: Monday, November 4, 2002 1:44 PM Consistent, yes, but that does not make your usage acceptable or elegant. Indeed I understand something different by handle, so I am nt even sure I agree with the consistent here. I think of handle as being a means of manipulating an object with state, and that is definitely very different from your idea that all private types can be called handle :-) **************************************************************** From: Robert A. Duff Sent: Saturday, November 2, 2002 9:19 PM Robert says: > I must say I *really* disklike the use of Handle in this spec, and > Set_Discrete.Handle does not read well either (sounds like a verb > acting on a noun). To me, "Handle" implies some sort of reference semantics. Like when you copy a "file handle" all over the place, and then all those places can write to the *same* underlying file. But these sets have value semantics. If I were writing this package, I would call it Discrete_Sets (or perhaps Generic_Discrete_Sets), and I would call the type Set. I would call the parameters "S: Set" or S1, S2: Set", since named notation is not interesting for these operations. I would use "Is_" in front of predicates where it makes sense (Is_Subset, Is_Member (or Is_In)). I would have the operators rename the identifier functions, rather than the other way around. Shouldn't there be an Is_Empty and Is_Full? Wouldn't "Universe" or "Universal_Set" be a more maths-appropriate name for Full? Are "+" and "*" really the appropriate operator symbols for Union and Intersection? Why not "or" and "and"? (Too bad we can't use the *real* maths symbols. ;-)) P.S. I'm having trouble keeping track of all the back-and-forth on these data structure packages. I plan to review the whole thing at some point -- I hope Randy will announce when the AI itself is more-or-less "ready". **************************************************************** From: Robert A Duff Sent: Saturday, November 2, 2002 12:49 PM Jeff Carter wrote: > Perhaps Handle has a technical meaning that I've been able to avoid > encountering in over 27 years of professional software development. I > use the term to refer to something that allows you to manipulate > something without knowing its implementation. By this definition, any > private type is a handle. I think "handle" means a *pointer* to some underlying data structure, where you don't know the representation of that data structure, and probably also don't know how it is allocated and deallocated. What your definition is missing is the "pointer" part. I don't know if I've ever seen this definition written down (in not quite 27 years, but getting close). I've just inferred it from usages I've seen. > The main use of Handle I have encountered is "file handle", which is > usually an integer returned as a result of opening or creating a file. > Clearly this integer is only a pointer to some hidden information that > allows a file to be manipulated. Thus, this use is consistent with mine. Right -- a file handle is a *pointer* into some OS table or whatever. I've seen similar usages for other OS resources that are manipulated by reference. If sets had reference semantics, then "Set_Handle" would be a good name (not just "Handle", please). But they do not -- if you do an assignment, you get a copy of the set. If sets are implemented internally using pointers, that fact should be invisible to clients, so it's *still* not a handle. Also, calling something just "Handle" basically means you can't use use_clauses. You would of course want to refer to Package_Name.Handle (and you would *have* to, if you are importing more than one of these data structure packages). It's fine for folks to write their own personal code in an anti-use-clause biased way, but for something that is intended to be part of the Ada standard, the names ought to allow either style. I suggest calling the package Bit_Mapped_Sets, and the type Set or Bit_Mapped_Set. We might want other representations of sets-of-discrete someday, and the choice among these packages is based on the implementation characteristics (efficiency concerns, basically). So I think it's appropriate for the implementation to show up in the name. Robert Dewar was apparently confused by the name: his recent comments make sense if the goal is "a general set package". For example, he didn't understand why the generic formal should be discrete, nor why the choice function should be ordered. But clearly the goal is "a set package using bit maps, and therefore restricted to discrete elements" -- and in practise, "relatively small range of discrete values". I presume "general set package" is also one of the data structures to be added to this group. **************************************************************** From: Robert A Duff Sent: Saturday, November 2, 2002 9:43 PM > Agreed, but can you think of better names? These names confused Pascal > Leroy, at least. Oops. I now see it was Pascal Obry, not Pascal Leroy. Sorry about that. May I now remove my foot from my mouth? Anyway, the point is that *somebody* was confused by these names, so perhaps they are not such good names. **************************************************************** From: Robert A Duff Sent: Saturday, November 2, 2002 9:25 PM > I've done a great deal of discrete set manipulation, perhaps more in > Pascal than Ada. This package is incomplete for serious applications. > > The first things needed are choice functions: > > function First (Set : Handle) return Element; > function Last (Set : Handle) return Element; > > These return the smallest (resp. largest) element of Set if Set isn't > empty. If Set is empty, Constraint_Error is raised. Agreed, but can you think of better names? These names confused Pascal Leroy, at least. Don't you also want a way to get the next (and previous) elements, so you can iterate through the set (without destroying it)? > It is highly desirable for the sake of efficiency to add something like > these as well: > > procedure Add_Element (Set : in out Handle; Elem : in Element); > procedure Remove_Element (Set : in out Handle; Elem : in Element); > > The procedure Remove_Element, plus First and Last, allows adequately > efficient iterators to be constructed in both directions (beware that > this method loses to the naive method for large enough sets). You can do > better in general if the iterators are added to the package. I would add iterators to the package. I must admit, I haven't been paying much attention to all these data structure packages flying by, but I think there's some advantage in having iterators in all (many?) of them, and making the iterator style uniform. Ada's support for iterators is pretty poor, IMHO, so that may not be easy. But having iterators doesn't necessarily mean the building blocks mentioned above should not *also* be present. **************************************************************** From: Robert A Duff Sent: Saturday, November 2, 2002 9:38 AM Robert says: > By the way, I see no reason to imply that sets are kept sorted, since > this requires a rather inefficient (from the point of view of average > performance behavior) storage structure. I don't understand what you mean by that. I thought the intended implementation was a bit map. The First operation has to search through the bits from Element'First until it finds a True bit. (There are bit-hacky ways of optimizing, like skipping whole-words of all-zero bits, but it's still basically a linear search.) I would expect many of these sets to fit in a single machine word. Set of Character might also be common. But not Set of Integer. The AI says these sets *could* be implemented as bit maps. I think it should go further, and make it official Implementation Advice that they *should* be implemented as bit maps. In general, I think all the data structure packages should indicate, via Impl Advice, what the intended implementation is. Otherwise programmers have no idea when a given data structure is appropriate to a given use. (Having a reference implementation also helps in this regard.) If there is a wide variety of implementation approaches across implementations, then these kinds of packages become almost useless. Also, whoever writes an ACATS test needs to know whether "Set of Integer" is a "fair" test case. Maybe the *name* of the package should also indicate the intended implementation. I mean, suppose we have another package that implements sets-of-discrete as an array-of-ranges (or pointer thereto). It should have an identical interface. But you can't call them both Discrete_Sets. In fact the *only* difference between the two is expected performance characteristics. (I say "expected", because we can't talk about performance normatively. But we can Advise about it.) **************************************************************** From: Michael F. Yoder Sent: Wednesday, November 6, 2002 8:44 AM I strongly agree with the above. Having spent some time in a job where the phrase "at wire speed" was much more than a mere buzzword, I've come to appreciate the fact that performance properties (e.g., time or space usage) often need to be part of the contract in specifications. **************************************************************** From: Robert A Duff Sent: Saturday, November 2, 2002 9:50 AM > And that's what I don't like. Conceptually sets are not ordered. I don't agree. Well, of course I agree that "conceptually sets are not ordered." But this is a set over an element type that is inherently ordered. I think it is useful to provide iterators (and iterator building blocks) over such sets that have deterministic order. If I iterate over the set of capital letters, I want it to go from 'A' to 'Z'. If the semantics doesn't specify that, then most implementations *will* go from 'A' to 'Z' (we're talking bit maps, here), and programmers will depend upon that fact, which is a bug. In general, I don't think gratuitous nondeterminism is a good idea -- it's error prone. ("Gratuitous" because in this case, there is no efficiency or other advantage to allowing implementation freedom. For other kinds of sets, where the element type has no inherent order, you may be correct that iterators should have nondeterministic order.) **************************************************************** From: Ted Baker Sent: Wednesday, November 6, 2002 12:39 PM I would hope that on machines where there is a bit-scan machine instruction that would be used. **************************************************************** From: Jeffrey Carter Sent: Tuesday, November 5, 2002 5:09 PM !wording A.X.12 Unprotected Unbounded Skip List Handling The language-defined package Ada.Data_Structures.Skip_List_Unbounded_Unprotected provides a generic package each of whose instances yields a limited private type Handle and a set of operations. An object of a particular Skip_List_Unbounded_Unprotected Handle type represents a sorted list structure whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Data_Structures.Skip_List_Unbounded_Unprotected has the following declaration: generic -- Ada.Data_Structures.Skip_List_Unbounded_Unprotected type Element is private; with function "<" (Left : Element; Right : Element) return Boolean is <>; with function "=" (Left : Element; Right : Element) return Boolean is <>; package Ada.Data_Structures.Skip_List_Unbounded_Unprotected is type Handle is limited private; procedure Clear (List : in out Handle); procedure Assign (To : out Handle; From : in Handle); type Result (Found : Boolean := False) is record case Found is when False => null; when True => Item : Element; end case; end record; function Search (List : Handle; Item : Element) return Result; procedure Insert (List : in out Handle; Item : in Element; Duplicates_Allowed : in Boolean := False); procedure Delete (List : in out Handle; Item : in Element); function Get_First (List : Handle) return Element; function Get_Last (List : Handle) return Element; function Is_Empty (List : Handle) return Boolean; function Length (List : Handle) return Natural; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (List : in out Handle; Context : in out Context_Data); private -- Ada.Data_Structures.Skip_List_Unbounded_Unprotected -- not defined by the language end Ada.Data_Structures.Skip_List_Unbounded_Unprotected; A skip list is a probabalistically balanced structure similar to a balanced tree in search time. Insertions and deletions are typically faster than balanced trees. Often type Element will be a record type with two parts, the "Key" and the "Value". The functions "<" and "=" will then be defined to only compare the Key parts of their parameters. An object of type Handle is initially empty. procedure Clear (List : in out Handle); Clear makes List empty. Postcondition: Is_Empty (List) Length (List) = 0 procedure Assign (To : out Handle; From : in Handle); If To and From are the same list, Assign has no effect. Otherwise, To is cleared and made into a copy of From. If, after clearing To, there is insufficient storage for copies of all the Elements in From, Storage_Exhaustion_Error is propagated. Postcondition: Length (To) = Length (From) Type Result defines the results of function Search. function Search (List : Handle; Item : Element) return Result; If there exists a value X stored in List such that X = Item, Search returns (Found => True, Item => X) Otherwise, Search returns (Found => False) procedure Insert (List : in out Handle; Item : in Element; Duplicates_Allowed : in Boolean := False); If not Duplicates_Allowed and there is insufficient storage available to store Item in List, Storage_Exhaustion_Error is propagated. Insert adds Item to List in the order specified by "<". If Duplicates_Allowed, Insert adds Item after any values in List which are = Item. If not Duplicates_Allowed and there is a value in List which is = Item, Insert replaces the value with Item. Postcondition: not Is_Empty (List) procedure Delete (List : in out Handle; Item : in Element); If List contains an Element X such that X = Item, Delete deletes X from List. Otherwise, Delete has no effect. If List contains more than one such Element, Delete deletes the Element that would be found by Search with Item. The user is expected to have checked for the existence of Item in List before calling Delete, using Search, Get_First, or Get_Last. function Get_First (List : Handle) return Element; If List is empty, Get_First propagates Empty_Structure_Error. Otherwise, Get_First returns the first value stored in List. function Get_Last (List : Handle) return Element; If List is empty, Get_Last propagates Empty_Structure_Error. Otherwise, Get_Last returns the last value stored in List. function Is_Empty (List : Handle) return Boolean; Is_Empty returns True if List is empty and False otherwise. function Length (List : Handle) return Natural; Length returns the number of Elements stored in List. generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (List : in out Handle; Context : in out Context_Data); An instance of this procedure applies Action to each Element stored in Over in turn in sorted order. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in Over. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a skip list while iterating over it. Possible consequences are: propagating Constraint_Error, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. Notes If duplicates are allowed during insertion, Delete may delete a different node than the one expected, with possible unexpected results. It is recommended that duplicates not be allowed if possible. !discussion The Action procedure supplied to Iterate is not allowed to modify the Element passed to it, since that could result in the list becoming unbalanced. This differs from the iteration procedures in the other data structures packages. The reference implementation of Ada.Data_Structures.Skip_List_Unbounded_Unprotected is PragmARC.Skip_List_Unbounded; it differs in its name and the names of types and exceptions. Type Handle is a controlled type in the reference implementation. !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a skip list. Once the entire file has been read, output the number of values read followed by all the unique values, in ascending order, to the standard output. declare package Integer_List is new Ada.Data_Structures.Skip_List_Unbounded_Unprotected (Element => Integer); procedure Put (I : in Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_List.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; List : Integer_List.Handle; use Integer_List; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Insert (Item => I, List => List); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Length (List) ) ); Put_All (Over => List, Context => Dummy); end; **************************************************************** From: Christoph Grein Sent: Wednesday, November 6, 2002 12:17 AM > procedure Insert (List : in out Handle; > Item : in Element; > Duplicates_Allowed : in Boolean := False); > > If not Duplicates_Allowed and there is insufficient storage available to > store Item in List, Storage_Exhaustion_Error is propagated. Bad wording: Storage_Exhaustion_Error is presumably also raised if Duplicates_Allowed, but there is insufficient storage. **************************************************************** From: Jeffrey Carter Sent: Wednesday, November 6, 2002 12:47 PM I'm not sure how I did that, but you are certainly correct. Thanks. **************************************************************** From: Robert Dewar Sent: Wednesday, November 6, 2002 9:43 PM > I don't understand what you mean by that. I thought the intended > implementation was a bit map. The First operation has to search through > the bits from Element'First until it finds a True bit. (There are > bit-hacky ways of optimizing, like skipping whole-words of all-zero > bits, but it's still basically a linear search.) A bit map is a horribly inefficient method for storing a sparse set. A hash table is the most natural method, although if the set is to be kept sorted a balanced binary tree or Btree could be used. If you require this to be a bit map implementation that severely restricts its utility. **************************************************************** From: Robert Dewar Sent: Wednesday, November 6, 2002 9:45 PM > In general, I don't think gratuitous nondeterminism is a good idea -- > it's error prone. ("Gratuitous" because in this case, there is no > efficiency or other advantage to allowing implementation freedom. False ... there is a definite advantage you can use breathing hash tables as the implementation, which is the appropriate and most general implementation The restriction to bit maps is a severe one. **************************************************************** From: Robert Dewar Sent: Wednesday, November 6, 2002 9:48 PM > Robert Dewar was apparently confused by the name: his recent comments > make sense if the goal is "a general set package". For example, he > didn't understand why the generic formal should be discrete, nor why the > choice function should be ordered. But clearly the goal is "a set > package using bit maps, and therefore restricted to discrete elements" > -- and in practise, "relatively small range of discrete values". I certainly saw no hint in previous messages that bit maps were intended. But certainly if you want a limited implementation, the names should make this very clear. **************************************************************** From: Robert Dewar Sent: Wednesday, November 6, 2002 9:48 PM > I strongly agree with the above. Having spent some time in a job where > the phrase "at wire speed" was much more than a mere buzzword, I've come > to appreciate the fact that performance properties (e.g., time or space > usage) often need to be part of the contract in specifications. That's certainly reasonable, but then at least the package name should strongly indicate the restricted domain. Something like package Bit_Map_Sets ... **************************************************************** From: Pascal Leroy Sent: Thursday, November 7, 2002 4:08 AM > A bit map is a horribly inefficient method for storing a sparse set. A hash > table is the most natural method, although if the set is to be kept sorted > a balanced binary tree or Btree could be used. > > If you require this to be a bit map implementation that severely restricts > its utility. My first reaction to Bob's comment was similar to Robert's. But then I gave it more thought, and I now agree that the underlying algorithm should be part of the contract. If I am building a set, the elements of which are discrete and can take 50 values, then I surely want a bit map implementation. While technically it involves a linear search, in practice it's going to be way more efficient than a hash- or tree-based implementation. If on the other hand the number of elements can be large, then I might want a hash table, or a skip list, or an AVL tree, you name it. At any rate the decision should be made by the programmer, not by the designers of the reusable components. In order to allow for easy substitution of an implementation for another, the exported operations should be the same regardless of the underlying implementation. The generic formal parameters could probably vary, though: a bit map only makes sense if the element is discrete; a hash implementation needs a hash function and an equality operator, but should not otherwise restrict the element type; a tree implementation needs a total ordering, but again should not restrict the element type. Incidentally, this leads me to think that we might need to organize these reusable components in two tiers: 1 - A low-level tier devoted to general purpose algorithms and data structures. Surely an AVL tree can have many purposes, but the rebalancing part of the insertion/deletion is hard to get right, so it makes a lot of sense to reuse it. The same is true to some extent of skip lists or hash table, although in these cases the algorithms are significantly simpler. 2 - A higher-level tier devoted to abstractions like sets (ordered or unordered), maps, and the like. Each of these abstractions would have a variety of distinct implementations, differing in the name of the generic and the details of the generic formal part. The visible parts would be independent of the implementation. I would expect that most applications could merely use tier 2 packages, but if the need arises they could also access the tier 1 packages to reuse fancy algorithms for specific purposes. **************************************************************** From: Jean-Pierre Rosen Sent: Thursday, November 7, 2002 4:23 AM > Incidentally, this leads me to think that we might need to organize these > reusable components in two tiers: Incidentally, there is a precedent here since the basic organization you are proposing looks like what has been done for strings: Various implementations with different properties and (almost) compatible specifications. **************************************************************** From: Robert A Duff Sent: Thursday, November 7, 2002 7:37 AM > I certainly saw no hint in previous messages that bit maps were intended. Go back and read the original proposal. It specifically says that bit-maps are an appropriate implementation. Besides, if the intent was a hashed set, why on Earth would the generic formal type be restricted to a discrete one? And why is there no Hash function passed in? > But certainly if you want a limited implementation, the names should make > this very clear. Yes, I want a limited implementation, and I agree the name should make that clear. I *also* want the kind of general-purpose hashed set that you have been pushing for. But I really think that's a *different* abstraction from the one we are currently discussing. I must admit, it is a little bit odd that we are worrying about the bit-mapped set *first*. Perhaps the general-purpose-set abstraction should be defined first, and *then* special-purpose ones like bit maps. Anyway, my main point is that as a programmer, I have to know the efficiency properties. If you give me a set type that might be implemented either as a bit-map *or* a hash table, I won't use it for a set of a small enum type, because a hash table is way too expensive for that. But I can't use it for "set of integer", either, because that might take up 2**32 bits per set. To be useful, I have to know which it is. **************************************************************** From: Robert Dewar Sent: Thursday, November 7, 2002 8:00 AM I absolutely agree that the nature of the implementation should be part of the spec. **************************************************************** From: Michael F. Yoder Sent: Thursday, November 7, 2002 9:11 AM >If you require this to be a bit map implementation that severely restricts >its utility. Considering bit maps to be of limited utility must come from a difference in style, or a difference in the sort of programs implemented. I've only used a hash table to implement a set a few times; I've more often used sorted lists or sorted arrays. If I tried to set down all the times I've used a Pascal set I couldn't remember them all. In Ada, I've used bit mapped sets for at least: set of priority set of chessboard square set of possible prime set of (middle) exponent of primitive trinomial set of color--a few instances set of command--many instances set of used enumeration value set of value mod N (with smallish N of course) set of token (in a parser generator) set of character It's true that bit maps are inefficient for sparse sets, but they're efficient for non-sparse ones, especially if there's no reason to think the elements will be clumped into ranges. And there are cases where any implementation other than a bit map is horribly inefficient compared to bit maps. >That's certainly reasonable, but then at least the package name should >strongly indicate the restricted domain. Something like >package Bit_Map_Sets ... That seems entirely reasonable to me. **************************************************************** From: Jeffrey Carter Sent: Thursday, November 7, 2002 2:05 PM > But I can't use it for "set of integer", either, because that > might take up 2**32 bits per set. Irrelevant to the discussion, but 2**32 bits is 512 Mbytes. Computers are being sold for home use right now with that much physical memory. We may soon be at the point that the amount of memory used is no longer a serious reason not to use a bit map for a set of integer. (Other considerations may very well argue against it.) **************************************************************** From: Robert Dewar Sent: Friday, November 8, 2002 11:26 PM Such as cache locality, a problem that gets *worse* with time, not better! **************************************************************** From: Robert A Duff Sent: Saturday, November 9, 2002 10:08 AM > Irrelevant to the discussion, but 2**32 bits is 512 Mbytes. Computers > are being sold for home use right now with that much physical memory. We > may soon be at the point that the amount of memory used is no longer a > serious reason not to use a bit map for a set of integer. Set of 64-bit Long_Integer? ;-) >...(Other considerations may very well argue against it.) Yeah. The point is that the programmer (not the language designer or implementer) should be making the decision, and needs to know the implementation details in order to do so wisely. P.S. When I talk about "memory usage" concerns, I'm talking about *speed* -- namely cache and paging effects. Well, maybe not for embedded systems, but certainly for desk-top computers, the issue is usually not "running out of memory". **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 10:17 AM I find this generally a dubious point of view for a high level language. Few programmers can for example know the proper trade offs for cache performance in a case like this, and furthermore such tradeoffs are severaly architecture and even specific hardware implementation specific. The oppositing point of view is that the programmer knows the functional requirements and the implementation supplies efficient implementation details. After all the above quoted argument could perfectly well be used in the following way The programmer (not the compiler designer or implementer) should be making the decision as to what instructions should be used, and needs to know the implementation details in order to do so widely. So while I am not necessarily disagreeing in this particular case, I object to the above quote as a general statement of principle :-) **************************************************************** From: Robert A Duff Sent: Saturday, November 9, 2002 11:12 AM > I find this generally a dubious point of view for a high level language. I don't take that view in general -- just for these kinds of data structure packages. > Few programmers can for example know the proper trade offs for cache > performance in a case like this, and furthermore such tradeoffs are > severaly architecture and even specific hardware implementation specific. > > The oppositing point of view is that the programmer knows the functional > requirements and the implementation supplies efficient implementation > details. Well, there are lots of incompetent programmers around, but I claim that at least *some* programmers can make educated guesses about whether to use bit maps or hash tables. After all, we all make those decisions all the time. I also claim that compiler writers and language designers can *not* make those decisions well, because they don't know the dynamic frequency of various operations, because it depends a lot on the particular application. Only the programmer of that application can guess (or measure) how sparse are typical sets, or whether Is_In is far more common than Intersection, for example. > After all the above quoted argument could perfectly well be used in the > following way > > The programmer (not the compiler designer or implementer) should be making > the decision as to what instructions should be used, and needs to know the > implementation details in order to do so widely. But compilers *can* choose good code sequences for array indexing and integer arithmetic and so forth. What compilers cannot do is decide whether an array or a linked list is the best choice. Maybe someday... > So while I am not necessarily disagreeing in this particular case, I object > to the above quote as a general statement of principle :-) OK, then we probably agree. I did not intend to make a *general* statement. **************************************************************** From: Pascal Leroy Sent: Saturday, November 9, 2002 1:52 AM > Irrelevant to the discussion, but 2**32 bits is 512 Mbytes. Computers > are being sold for home use right now with that much physical memory. We > may soon be at the point that the amount of memory used is no longer a > serious reason not to use a bit map for a set of integer. (Other > considerations may very well argue against it.) This statement doesn't make sense to me. I don't want to think of the time it would take to compute the union of two such sets for instance. A loud alarm goes off in my head when I see O(N) time complexity. **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 8:55 PM And a loud alarm goes off in mine when I realize that this computation of a union of interesection would be 100% out of cache :-) **************************************************************** From: Nick Roberts Sent: Friday, November 8, 2002 9:09 AM Michael F. Yoder wrote: > Pascal Obry wrote: > >> >> > function First (Set : Handle) return Element; >> > function Last (Set : Handle) return Element; >> >> This is of course not really needed in Ada since we have attributes >> 'First, >> 'Last, 'Succ and 'Pred. >> > I may have made a poor choice of names here, contributing to confusion. > Perhaps Min and Max would be better. It seems to me the names should be Bottom and Top, from a mathematical point of view. I would prefer the following alternative arrangement, however: a function Choose, which returns an arbitrarily chosen member of a set; a procedure Extract, which chooses an arbitrary member (as for Choose), but also removes it from the set; a passive iterator (is this already included?) generic procedure; an active iterator type (preferably by the kind of abstract tagged type derivation scheme I suggested to the SCL people a couple of years ago). function Choose (Source: in Set) return Element; procedure Extract (Source: in out Set; Chosen: out Element); Both of these raise an exception if the set is empty. Possibly the names Choose_One and Extract_One would be preferable. Or maybe Pick_Member and Pick_and_Remove? > In fact, Min could be defined to return Element'Last for the empty set, > and Max could return Element'First. This would *sometimes* be preferable > to raising an exception. To be honest, in my coding I've generally > preferred raising Program_Error in that case, since trying to get an > element out of the empty set feels like an offense against all of > mathematics. :-) However, these aren't general sets, and the alternate > definition has the right mathematical properties; No it doesn't (when the actual type for Element has only one value). > it's like defining the > sum over an empty set to be zero, or the product to be unity. With a passive iterator, you get the right semantics in the case of an empty set (the per-item operation is simply never executed) without any effort. Similarly, with an active iterator you just get and End_of_Data indication immediately. **************************************************************** From: Nick Roberts Sent: Friday, November 8, 2002 10:21 AM Robert Dewar wrote (replying to Ted Baker): > < make it clear how I read Michael's usage of the term "nondeterministic", > which we all know means different things to different people. > My own preferred usage is precisely as in nondeterministic Turing > Machine and NDFA. However, it seems to me most people use the term > in a broader sense, to including the outcome being one of a range of > values, from which a different value may occur on each execution. > I interpreted Michael's comments as following that usage, i.e., > that the choice operation be a function of the set, in the > mathematical sense.>> > > Sure but the issue is whether it is a defined function. For example, it > is crucial in SETL that the formal semantics is non-deterministic, and > any program depending on repeatable results is considered incorrect. > > Note that we have an analogy in Ada. The task chosen in a sleect > statment is chosen non-determinisitically in the base language, but > this does not mean non-repeatably. I hope I'm correct in surmising that Robert Dewar is not in favour of the interface to the set package---albeit one which is specifically over the values of a discrete type, and, putatively, specifically to be implemented as a bitmap---having characteristics that are based on some ordering of the members of the set (e.g. the ordering inherent in the base type). In which case I agree, and I think for a pretty straightforward practical reason: interchangeability of implementation. If you write a program which uses a bitmap set package for a certain purpose (perhaps mainly because you anticipate its base type's range will be small, and so objects of the set type will be small in size), and later find that you wish to change the implementation of that set type to something else (e.g. based on hashing or a tree, perhaps because the size of the set has become large), you will find difficulty in doing so if your code is peppered with operations that seemed to make sense for the bitmap operation but will be difficult or very inefficient within the new implementation. I would suggest that interchangeability of implementation is one of the most valuable advantages of ADTs, and that this applies in this case, in reality, not just in theory, in Spades as they say. **************************************************************** From: Michael F. Yoder Sent: Friday, November 8, 2002 5:26 PM Nick Roberts wrote: > Michael F. Yoder wrote: > >> Pascal Obry wrote: >> [snip] > >> I may have made a poor choice of names here, contributing to >> confusion. Perhaps Min and Max would be better. > > > It seems to me the names should be Bottom and Top, from a mathematical > point of view. I don't see why. This isn't a general lattice, and it seems to me the more specific names would be better. > I would prefer the following alternative arrangement, however: a > function Choose, which returns an arbitrarily chosen member of a set; > a procedure Extract, which chooses an arbitrary member (as for > Choose), but also removes it from the set; a passive iterator (is this > already included?) generic procedure; an active iterator type > (preferably by the kind of abstract tagged type derivation scheme I > suggested to the SCL people a couple of years ago). > > function Choose (Source: in Set) return Element; > > procedure Extract (Source: in out Set; Chosen: out Element); > > Both of these raise an exception if the set is empty. Possibly the > names Choose_One and Extract_One would be preferable. Or maybe > Pick_Member and Pick_and_Remove? > >>> In fact, Min could be defined to return Element'Last for the empty >>> set, and Max could return Element'First. [snip] However, these >>> aren't general sets, and the alternate definition has the right >>> mathematical properties; >> > No it doesn't (when the actual type for Element has only one value). Yes it does. The property is that Max (S + {e}) = max (Max(S), e) for all sets including S = {}. Similarly for Min. > > it's like defining the > >> sum over an empty set to be zero, or the product to be unity. > > With a passive iterator, you get the right semantics in the case of an > empty set (the per-item operation is simply never executed) without > any effort. Similarly, with an active iterator you just get and > End_of_Data indication immediately. I don't object to having iterators in addition; I'd prefer it. But in nearly every use I've made of bit mapped sets I've needed not just any choice operator, but specifically Min, or Max, or both. In some cases (like set of priority) this is because the function wasn't being used as a choice function particularly. **************************************************************** From: Michael F. Yoder Sent: Friday, November 8, 2002 6:07 PM > I would suggest that interchangeability of implementation is one of > the most valuable advantages of ADTs, and that this applies in this > case, in reality, not just in theory, in Spades as they say. On the contrary, I've used bit mapped sets a great deal, and this consideration just doesn't come up. *Every* instance of a bit mapped set I've used, I've needed Min and/or Max. Sometimes, I've needed an efficient Min or Max. Not once have I needed to generalize, much less generalize to an implementation that couldn't supply Min and Max straightforwardly. In a previous message I cited more than a dozen cases of actual Ada use, and noted that the number would increase significantly if I included Pascal cases. Why would it be likely that sets of an enumeration type (command, color, or whatever), of priorities, of primes, of (necessarily integer) exponents, or most such cases, are at all likely to need generalization? Even when it happens, it seems negligibly probable that you'll want to generalize in such a way that Min and Max become hard to implement. What evidence, anecdotal or otherwise, is there to oppose this claim? But even beyond this, I disagree that it's wrong to supply Min and Max in other cases. It's better to have them in *every* set package where the type comes with a singled-out full ordering, including private types with a generic formal "<" supplied for the type. If, for some implementations, Min and Max aren't necessarily efficient, so be it: document that possibility. **************************************************************** From: Robert Dewar Sent: Friday, November 8, 2002 6:57 AM > If I am building a set, the elements of which are discrete and can take 50 > values, then I surely want a bit map implementation. While technically it > involves a linear search, in practice it's going to be way more efficient than ahash- or tree-based implementation. Actually that's not true, even with 50 elements, an iteraction can be significantly slower if very few elements are in the set. Suppose for instance that just one element is present, then it can take 50 shifts and tests to find that one element, as opposed to a single direct reference with other data structures. But of course in general you are correct, for small sets, a bit map implementation is the appropriate one, and operations like union and intersection are faster with a bit map implementation even for very large sets if the sets are not too sparse. In SETL, this is handled by the equivalent of rep clauses. There is a single set type, and you give a representation clause (which is semantically neutral) saying what implementation you want. Then you can mix operations between sets with different representations. The same thing could of course be done in an Ada package. You would have to do the represention test dynamically but that's not terrible. A package which assumes bit representation is reasonable, although note that such data structures are familiar and reasonably convenient in Ada now as it stands: procedure colors is type color is (red, green, blue, yellow, orange); type cset is array (color) of Boolean; pragma Pack (cset); s,t,u : cset; begin s := (red | blue | green => True, others => False); t := (red | green | orange => True, others => False); u := s and t; -- set intersection end; so we should make sure that the package is at least as convenient to use :-) **************************************************************** From: Robert A Duff Sent: Friday, November 8, 2002 8:16 PM Yes. This is a very good point. **************************************************************** From: Michael F. Yoder Sent: Friday, November 8, 2002 9:27 PM I agree with these statements. I think they imply that you want a function that returns the set containing just the values in a specified range, to correspond to s := (red .. green => True, others => False); **************************************************************** From: Robert Dewar Sent: Friday, November 8, 2002 11:49 PM I am confused .. the above is a perfectly respectable statement. **************************************************************** From: Michael F. Yoder Sent: Saturday, November 9, 2002 6:26 AM I meant that, if s were instead of type Inst.Handle for some instantiation Inst of Set_Discrete, you would want to be able to say something like s := Element_Range (red, green); Otherwise the package wouldn't always be "at least as convenient to use," as you put it. **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 6:46 AM OK, I see, and indeed the call to Element_Range is a bit uncomfortable compared with the more syntactically familiar use of red .. green. And it's worse than that. Proper usage for subranges in enumeration types is never to use red .. green explicitly in client code. Why? Because this means that clients are depending on the exact ordering and to change the enum type you have to rummage all over the place for range usages like this (of course an ASIS tool can help find these usages, but it's still unpleasant). Better is to define *with* the subtype all legitimate subtypes, e.g. type color is (red, green, blue, cyan, magenta, yellow); subtype rgb is color range red .. blue; subtype photo_primaries is color range cyan .. yellow; And now we can allow a client to write an aggregate as: s := (rgb => true, others => false); And then your element range must become: s := Element_Range (rgb'First .. rgb'Last); Element_Range here is really quite unpleasant. It encourages the usage corresponding to a .. b in open code, with the additional disadvatanges that it is not possile to write simple ASIS applications to catch such bad usage. **************************************************************** From: Michael F. Yoder Sent: Saturday, November 9, 2002 7:27 AM Well, all this may be true, but it doesn't much affect the question. I only chose that particular enumeration type in the example because it is what you had used. It's certainly legitimate to use ranges like 'a' .. 'z', '0' .. '9', or 'A' .. 'F' (for hex digits). Similarly if an instance used an integer subtype, using an explicit range would be fine. And, when you do define subtypes, there remains the question of how to use them in the context of the package. You still need the function, presumably used by saying something like Element_Range (rgb'first, rgb'last). **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 8:33 AM Yes, which is ugly, inconvenient, and methodologically unpleasant (because it is a hidden use of a range). So this is a point at which the straight Ada code is clearly superior to the package interface (one thing you always risk sacrificing in a package is nice syntax -- after all you can't even do a decent array abstraction at the package level, because of not having assignment syntax). Of course this is just one point of comparison, but the comparison needs to be made at all stages of the design. Here is what I would propose. If you decide that you are going to use a bit mapped implementation, what is the point of hiding the implementation as private. If you expose the implementation as a packed array of Boolean, then you can still have the package provide useful new functionality (not quite sure now what is in that category if anything) while retaining the nice language syntax available in aggregates, slices etc. **************************************************************** From: Robert A Duff Sent: Saturday, November 9, 2002 9:53 AM I think that's probably a good idea (given Ada's lack of support for building abstractions :-( ). I'm not sure what use slices are for sets, but certainly aggregates and array-indexing notations are useful. If you end up with *no* subroutines left, then obviously the package is not needed. But I suspect there will be some left (iterator support, for example). I also think it might be a good idea to drop work on the Bit_Map_Sets abstraction until a more general set package has been defined (implementation based on hashing, or something). There is some value in making all set-like abstractions use the same naming conventions and whatnot, where possible. I must admit I have not been paying close attention to the many data structure packages Jeff Carter has been sending out. So maybe he has *already* defined the general set package. **************************************************************** From: Robert A Duff Sent: Saturday, November 9, 2002 9:53 AM I wrote: > I'm not sure what use slices are for sets, but certainly aggregates and > array-indexing notations are useful. Well, I take that back, in part. I prefer this: X := Make_Set((Red, Orange, Yellow)); if Is_In(Red, X) then ... to this: X := (Red | Orange | Yellow => True, others => False); if X(Red) then ... (Make_Set takes an array-of-Element.) On the other hand, I'm perfectly happy to use "and" and "or" for intersection and union. **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 10:31 AM > I'm not sure what use slices are for sets, but certainly aggregates and > array-indexing notations are useful. A slice is a subset, for example, in my colors example, you can use a slice to convert a set-of-color value to set-of-rgb value retaining the rgb values present in the original (all this kind of thing is incidentally quite familiar to me, I have always regarded Ada has having quite acceptable support for this kind of set. As to whether there is useful functionality left, perhaps ... Iterators might be marginally more convenient but really it's quite acceptable to do for J in Setval'Range loop if Setval (J) then ... end if; end loop; I am not sure you can do much better with the package, especially if the interface is to declare some local iterator object, since that requires declare/begin/end overhead. As for Bob's :-( lament about not being able to build abstractions in Ada, I think that's overdrawn. Indeed there is a real danger in languages that allow too much syntactic abstraction, because then people start to essentially create sublanguages that are out of style with Ada. An example of doing this is GNAT.Spitbol.Patterns, but this is a very concious effort to create an abstraction that has a SNOBOL4 rather than an Ada flavor. I think that's not so bad in the case where you are mimicking an existing language. But suppose this package instead mimiced "dewars own not very well documented and not very well thoughout language xxx". Well that's not such a good idea at all. For example, suppose I allow := to be redesigned quite generally, and then, being a functional programming fan, I decide to make it's semantics equivalent to LET in scheme, rather than an assignment. I think that would be rather horrible :-) **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 10:40 AM > Well, I take that back, in part. I prefer this: > > X := Make_Set((Red, Orange, Yellow)); > if Is_In(Red, X) then ... > > to this: > > X := (Red | Orange | Yellow => True, others => False); > if X(Red) then ... I hope you have a good compiler .... In GNAT, the second assignment would generate a load immediate of a constant and a store (unless X was a register, in which case no store would be needed) But it would be quite a trick to get that same code out of Make_Set, even if it was inlined, certainly way beyond what you can expect from GNAT. After all we are talking about some non-trivial loop in the implementation of Make_Set here :-) So what you end up doing is manually generating a constant for that set which adds to the syntactic overhead. Now to be fair, I do see a way that the compiler can manage to manufacture these constants itself, since Make_Set is pure and can be pulled out of a loop so at least you don't get repeated evaluation (note however, this is still not the same as a load immediate :-) I agree that the top syntax is marginally nicer than the bottom, but I am not sure that marginal is good enough if all you are doing is duplicating standard language features. One advantage of the bottom two lines is that all Ada programmers understood them and were completely familiar with them BEFORE we started the entire discussion about a bit mapped set abstraction. As I said earlier, I have always thought of Ada as having perfectly fine facilities for handling this kind of bit mapped set and it's hard for me to get much interested in a package that so far does not seem to offer anything significant I do not already have! **************************************************************** From: Robert A Duff Sent: Saturday, November 9, 2002 11:01 AM > I hope you have a good compiler .... In my experience, "set literals" are far less (dynamically) common than the "Is_In" operations. I agree with you that turning Make_Set into a load immediate is too much to expect from compilers. On the other hand, the Is_In operation can easily be translated into the same code as the bottom example -- just Inline it. > I agree that the top syntax is marginally nicer than the bottom, but I am not > sure that marginal is good enough if all you are doing is duplicating standard > language features. One advantage of the bottom two lines is that all Ada > programmers understood them and were completely familiar with them BEFORE > we started the entire discussion about a bit mapped set abstraction. That's a good point. **************************************************************** From: Nick Roberts Sent: Saturday, November 9, 2002 11:21 AM Robert Dewar wrote: > Here is what I would propose. If you decide that you are going to use a bit > mapped implementation, what is the point of hiding the implementation as > private. If you expose the implementation as a packed array of Boolean, then > you can still have the package provide useful new functionality (not quite > sure now what is in that category if anything) while retaining the nice > language syntax available in aggregates, slices etc. Actually, one kind of package that could be in a similar category is an XML package, which exposes the entire tree structure (all the records and pointers) in the spec, and provides the extra functionality of parsing text into a tree and writing a tree out as text (the implementations of which are private, in the body). (This isn't necessarily the best design, I hasten to add, but it is a reasonable possibility.) **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 11:49 AM Possibly, but I really see no connection with the point I made, so perhaps you are reading something into what I said that is not there at all. **************************************************************** From: Michael F. Yoder Sent: Saturday, November 9, 2002 12:39 PM Robert Dewar wrote: >>And, when you do define subtypes, there remains the question of how to >>use them in the context of the package. You still need the function, >>presumably used by saying something like Element_Range (rgb'first, >>rgb'last). > >Yes, which is ugly, inconvenient, and methodologically unpleasant (because >it is a hidden use of a range). Robert, this last bit is pure begging the question. A subtype *is* a range, in this context. And as for ugly and inconvenient, the zillions of copies of "=> True, others => False" that I and others are always writing surely fall into the same boat. >So this is a point at which the straight Ada code is clearly superior to >the package interface (one thing you always risk sacrificing in a package >is nice syntax -- after all you can't even do a decent array abstraction >at the package level, because of not having assignment syntax). > >Of course this is just one point of comparison, but the comparison needs to >be made at all stages of the design. No argument there. An additional benefit to Element_Range (including perhaps a generalization along the lines of the second version of Ada.Strings.Maps.To_Set) is that the bounds needn't be static. (In fact, following precedent, I should rename the function To_Set and have its argument work the same way.) >Here is what I would propose. If you decide that you are going to use a bit >mapped implementation, what is the point of hiding the implementation as >private. If you expose the implementation as a packed array of Boolean, then >you can still have the package provide useful new functionality (not quite >sure now what is in that category if anything) while retaining the nice >language syntax available in aggregates, slices etc. That might well work. I want to think some before reacting to it though. When I've needed performance from similar packages, what I've wanted was that the objects were word or doubleword aligned (and that the "and", "or", "xor" functions took advantage of this) and that the Min and Max functions (whatever they were called) used the machine's find-first-bit instruction. I've done this last by using inlined code statements in Ada, or assembly calls in Pascal. **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 5:12 PM > Robert, this last bit is pure begging the question. A subtype *is* a > range, in this context. And as for ugly and inconvenient, the zillions > of copies of "=> True, others => False" that I and others are always > writing surely fall into the same boat. You very much miss the point, I will summarize why then you can go back and reread what I said before with a different point of view. The => True, others => False notations are a) completely independent of ordering of the enumeration type b) work just fine if new elements are added to the enumeration type Note that if you are concerned about forcing consideration of whether the new element should or should not be in the set, that too is possible by using (a | b | c => T, d | e | f => F) (the package interface should have this same possibility) Now if the elements are added or subtracted from the enuemration type aggregates in the above form are automatically illegal and have to be examined. But ranges in the form of explicit ranges such as a .. b are a) very dependent on the ordering of the enumeration type b) do not necessarily work as expected if new elements are added In Ada, there is a very unfortunate (in my view) limitation that subtypes of enumeration types must be ordered contiguous subsets. In general I find enumeration types far *too* ordered in Ada, forcing accidentaly over-specification. As a result, when defining subtypes, you often have to jigger around with the ordering of the elements to achieve contiguous subtypes (for an example of this in action see the definition of type Node_Kind in Sinfo in the GNAt sources). This means that when a new element is added to an enumeration type, it is often the case that you have to mess with the ordering. For this reason, good practice is to completely avoid the use of explicit subtype ranges in clients, since his introduces a severe maintenance liability. Now if the ranges are explicit ranges as in a .. b, we can at least have an ASIS tool that scans the sources for appearences of ranges. But if we introduce miscellaneous packages where we have ranges that are not semantically apparent at the source level, then the maintenance burden is increased. So to me, if the package introduces such "bogus" ranges, I would consider that good coding standards would preclude the use of these particular features in the package. I hope the difference between the two cases is now clear! **************************************************************** From: Michael F. Yoder Sent: Saturday, November 9, 2002 8:28 PM No, I didn't miss either point, I just don't agree with you. The pretense that others have "missed your point" is a common rhetorical device with you that I'm very tired of. But I won't go beyond calling a spade a spade. Your "explanation" is wrong in parts (a) and (b) if ranges are used in the aggregate. (It's also wrong if you substitute subtypes for the ranges.) Since this was the instance that initiated my observation in the first place, the rest of your explanation isn't relevant. This includes the implicit reference to unordered enumeration types, a feature I frankly don't care about. You also keep harping on enumeration types and ignoring modular types and integer types. I doubt anyone will ever add a new integer value between 3 and 4, or argue for redefining Ada's "<" operator on integers. If you think ranges are always evil for enumeration types (including character types?), fine, make your ASIS tool enforce that. To repeat what I said in a previous message, I only used an enumeration type in my example because I was copying it from yours. **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 8:49 PM <> Gosh, I am amazed at the problems in understanding here. I will try to be clearer. The whole point is NOT to use ranges like a..d in aggregates. That's precisely the usage you want to prohibit for enumeration types. Instead you insist on a subtype being declared for the range, and you use the subtype in the aggregate. The issue is that for enumeration types, you do not want to allow clients to use a..b in *ANY* context in a client, including aggregates. You always want to insist on named subtypes being declared, so that when you add elements to the enumeration type, you see the set of subtypes that has to be considered. If someone disobeys this rule and uses a..b in a client (including in an aggregate), then a simple ASIS tool can find the violations of the rule. Sure, for integer types, there is no reason to have this restriction and it is fine to use explicit ranges of course. <> ARRGH! The whole point is that the ASIS tool will not *see* a range if you write Set_Range(a,b) because there is no explicit range present. So using normal familiar Ada syntax for dealing with sets of discrete types represented as bit maps, the typical coding rules would be to prohibit explicit ranges for enumeration types (and insist on subtype names being used instead), and to allow them for integer types. But no analogous rule is possible for the package syntax. I guess what you could do in the package is to have a nested generic which gets instantiated with the required subtype, but although this solves the semantic problem, it is heavy. Here's my proposal. Why doesn't someone who favors this package write an example of its use. Then we can look at what the usage would be without the package and compare to see whether the package is really buying enough to be worth whlie (so far I am unconvinced). **************************************************************** From: Michael F. Yoder Sent: Sunday, November 10, 2002 4:20 PM >ARRGH! The whole point is that the ASIS tool will not *see* a range if >you write Set_Range(a,b) because there is no explicit range present. It would if I wrote the functional spec for the tool. Assuming uses of Set_Range are regarded as just as evil as use of ranges, and for the same cases, which I presume isn't putting words into your mouth. It's not surprising that I would "miss" this point you are presenting as obvious. >But no analogous rule is possible for the package syntax. Why not? ASIS can look at semantics as well as syntax. >I guess what you could do in the package is to have a nested generic >which gets instantiated with the required subtype, but although this >solves the semantic problem, it is heavy. If having a subtype banishes the evil, you could do the same by using a constant set. Instead of: subtype blah is bleah range x .. y; package blah_set_container is new subtype_set (blah); you write: blah_set : constant Set := Set_Range (x, y); or perhaps hex_digit : constant Set := Set_Ranges ((('0', '9'), ('a', 'f'), ('A', 'F'))); **************************************************************** From: Robert Dewar Sent: Sunday, November 10, 2002 4:44 PM > It would if I wrote the functional spec for the tool. Assuming uses of > Set_Range are regarded as just as evil as use of ranges, and for the > same cases, which I presume isn't putting words into your mouth. It's > not surprising that I would "miss" this point you are presenting as obvious. Sure, you can write a very specialized ASIS tool that will know about the exact set of packages that are in use and test for special cases like this. But that ups the ante considerably (the standard ASIS tool for looking for subranges is on the shelf and is independent of packages used). Furthermore, it is worse than that. The idea of the ASIS tool is to check for violations of the coding standard that forbids such usage in the first place. You can most certainly forbid the use of explicit ranges a..b since they can always be replaced by subtypes without any loss of functionality, but the use of Set_Range cannot be similarly replaced (that's why I suggested the possibility of a generic to get over this problem -- I assume the details are obvious -- but it's a bit heavy. There I hope my point (not that big a point, but one that has been hard to get understood) is now clear. There is a definite problem with the use of Set_Range. In general there is always likely to be a loss of convenience and expressive power when we replace a standard language feature with a package interface. That's not surprising, if there was no such loss, why would the language feature be there in the first place? :-) **************************************************************** From: Robert Dewar Sent: Sunday, November 10, 2002 4:46 PM > If having a subtype banishes the evil, you could do the same by using a > constant set. Instead of: > > subtype blah is bleah range x .. y; > package blah_set_container is new subtype_set (blah); > > you write: > > blah_set : constant Set := Set_Range (x, y); Sorry I don't understand at all, the definition of blah_set here does not use the subtype name so what use is it to solve the problem? **************************************************************** From: Michael F. Yoder Sent: Sunday, November 10, 2002 5:14 PM You use a set instead of a subtype. So, "Is_In (x, blah_set)" rather than "x in blah." This also allows having a conceptual subtype that isn't a range, as in the Hex_Digit example. Or, if you want to keep the syntax "x in blah" for efficiency, or aesthetics, or whatever, write subtype blah is bleah range x .. y; blah_set : constant Set := Set_Range (blah'first, blah'last); which is unaesthetic but only occurs once. Then blah_set is available for unions and intersections and whatnot, but you still write "x in blah" for membership. **************************************************************** From: Robert Dewar Sent: Sunday, November 10, 2002 5:38 PM Well I think you still misunderstand, since the whole idea is that the subtype declaration is NOT at the point of the Set_Range call, but at the point of definition of the type bleah, but never mind. Yes, you could enforce that blah'first, blah'last style as I mentioned in a note earlier (many notes earlier in this thread). Anyway, my view is the following. It is not worth (at least not worth my time) discussing this in abstract any further. What would be constructive is for someone (Michael?) to present a complete worked out example of a client using this package. Then we can compare this with what can be done within the language and see if the package provides sufficient added value to be worth while (my position, as stated earlier, is that I am dubious that this is the case). Certainly no package should be accepted without such worked out examples. It is not good enough that someone thinks it might be useful :-) **************************************************************** From: Michael F. Yoder Sent: Sunday, November 10, 2002 7:51 PM >Well I think you still misunderstand, since the whole idea is that the >subtype declaration is NOT at the point of the Set_Range call, but at >the point of definition of the type bleah, but never mind. No, I thought of mentioning that case explicitly, but decided it would be belaboring the obvious. :-) It falls into the "whatever" class of cases I mentioned. >Anyway, my view is the following. It is not worth (at least not worth my >time) discussing this in abstract any further. What would be constructive >is for someone (Michael?) to present a complete worked out example of a >client using this package. Then we can compare this with what can be done >within the language and see if the package provides sufficient added value >to be worth while (my position, as stated earlier, is that I am dubious >that this is the case). Certainly no package should be accepted without >such worked out examples. It is not good enough that someone thinks it >might be useful :-) Sigh. My position is, it's useless unless it contains at least the Min and Max operations and also has pretty strong efficiency guarantees. If a box-checking implementation suffices, boolean arrays are usually good enough. Thus, it's pointless to provide the package unless it's designed to be better than that. (Actually, that's not quite fair. The package can also provide operations that don't require non-static arguments, analogous to the To_Set functions in Ada.Strings.Maps.) I'll put down the instances I can recall off the top of my head. The discussion would be advanced if others contributed cases of existing bit map use. The instance with the strongest performance requirements would be a scheduler, where the relevant set is a set of priorities. Take a multiprocessor implementation, and use the usual gambit of having sets of nonempty queues. You use Max to find the queue with the highest priority runnable thread or task, and Min to find the lowest priority running thread or task. (On a uniprocessor, there's only one thread running, so Min needn't be used.) The important operations are: delete an element, add an element, Min, and Max. You might use set union for some species of synchronization mechanisms or timer queue implementations. For the "set of chessboard square" example, the application was an endgame analyzer. Speed was very important, and the most important operations were "and", "or", set difference, and "not". With a good compiler you might get speed as good as hand code by putting an alignment clause on the boolean array type. A choice function was needed, and doing this by iteration was painful enough that assembly language was used. A third case was a tool that computed transitive closures of the "called" relation for subprograms in ELF files. First, the subprograms were numbered, so sets could be bit vectors. Then, it crunched a lot. I don't remember what operations were most used; performance did matter. I think it implemented the choice function by passing 32-bit array slices to a library routine written in assembly that gave access to the "find first bit" instruction. For many of the other cases I mentioned, box-checking implementations would suffice. If I were implementing the package, I might not use a boolean array. If I didn't think I could cajole the compiler into making good code for boolean arrays, I'd round up the set size to a multiple of (say) 32 and use an array of a modular type, aligned to a word boundary. **************************************************************** From: Robert Dewar Sent: Sunday, November 10, 2002 8:34 PM There is no reason to think an array of modular type would be handled more efficiently than a packed array of bits. What makes you think this? **************************************************************** From: Michael F. Yoder Sent: Sunday, November 10, 2002 8:55 PM Of course there's a reason. If a compiler uses insufficiently optimized code generation for "and" and "or", it won't take advantage if you maximally align the bit array type. It probably will use the alignment of the modular type. I encountered this behavior in an old Ada compiler (I think it was a VADS port). It is possible that all or nearly all Ada compilers no longer have this problem. **************************************************************** From: Robert Dewar Sent: Sunday, November 10, 2002 9:18 PM Well in any case, even if the external representation of the type is as a packed boolean array, that does not prevent you from generating any code you like to efficiently access this boolean array in the body of the package. **************************************************************** From: Michael F. Yoder Sent: Sunday, November 10, 2002 10:17 PM Now I need some context. Does this mean overriding predefined "and" and "or" in the package spec? Or is this using a minimal modification of the existing spec, so "+" and "*" would get efficient implementations and "and" and "or" not-necessarily efficient ones? **************************************************************** From: Pascal Leroy Sent: Saturday, November 9, 2002 2:03 AM > Actually that's not true, even with 50 elements, an iteration can be > significantly slower if very few elements are in the set. Suppose for > instance that just one element is present, then it can take 50 shifts > and tests to find that one element, as opposed to a single direct > reference with other data structures. Well, the technique you describe is a linear search, so it's not surprising that it takes O(N) steps. You can do the equivalent of a binary search by "and'ing" with well-chosen masks. Take the case of a 16-bit word. You would first "and" with 16#00ff# and 16#ff00# to determine if the first bit is in the upper or lower half; then "and" the result with 16#0f0f# and 16#f0f0# to determine in which quarter of the set the first bit is located; etc. This technique requires O(Log(N)) steps and O(Log(N)) masks (which can be precomputed based on the word length of the underlying architecture). **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 9:00 PM Well O(Log(N)) where N is not the number of items in the set, but the number of items in the domain. So that's still not very good if there are zero items in the set. Furthermore, the business of using these masks slows things down considerably if the set is *not* sparse, so I doubt it's practical in practice, yes of course it degrades to O(N) in such a case, but with a much bigger constant. But anyway, this subpart of the discussion is not really relevant, I think we have agreed for now that the package we are talking about will be committed to using a bitstring implementation anyway. **************************************************************** From: Jeffrey Carter Sent: Saturday, November 9, 2002 1:51 PM Robert A Duff wrote: > I must admit I have not been paying close attention to the many data > structure packages Jeff Carter has been sending out. So maybe he has > *already* defined the general set package. The stack package I sent out today is the last of the data structures in the PragmARCs. In keeping with the ARG's stated preference for "APIs which have been available for years and have benefited from feedback from a large user base" with a "publicly available reference implementation", I have limited myself to presenting only existing data structures, but with certain name changes Randy recommended. The remaining data structures in the PragmARCs are protected bags, lists, and stacks. I'll get to them next unless there is a general desire not to see them. The set package is intended to mimic Pascal's sets. The operators used are those used by Pascal. The initial users were more comfortable with the operators than with remembering that set difference is "and not". The named functions are renamings of the operators because that's how the package evolved: The initial users were comfortable with the Pascal operators; a later user requested less cryptic names. This may not be as elegant as a new package created from scratch, but it's what you get when you ask for "APIs which have been available for years and have benefited from feedback from a large user base". There is no general set package because there has not been any need for one expressed by the PragmARCs' users (most of the PragmARCs were initially created to meet the needs of real projects). Concerning type names, if I use the existing Ada.* packages as a model, I would have Unbounded_Bag, [Un]Bounded_List, [Un]Bounded_Queue, Discrete_Set (or Bit_Mapped_Set from package Set_Bit_Mapped if there's consensus for that name change), Unbounded_Skip_List, and Unbounded_Stack. If there is consensus for these in place of Handle we can instruct Randy to make the changes. If the set package is going to specify a bit map, should the private part be specified? How about the implementation? **************************************************************** From: Robert A Duff Sent: Saturday, November 9, 2002 3:54 PM I think it's enough to say: Implementation Advice The intended implementation of Bit_Map_Set is as a bit map; that is, as a packed array of Booleans. or perhaps, "Bit_Map_Set should be implemened as...". Or perhaps take Robert's suggestion of not making the type private. I don't think it's necessary or beneficial to give all the subp bodies. P.S. Thanks for doing this work, and putting up with everybody griping at you about the details. ;-) **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 5:21 PM > > The stack package I sent out today is the last of the data structures in > the PragmARCs. In keeping with the ARG's stated preference for "APIs > which have been available for years and have benefited from feedback > from a large user base" with a "publicly available reference > implementation", I have limited myself to presenting only existing data > structures, but with certain name changes Randy recommended. The > remaining data structures in the PragmARCs are protected bags, lists, > and stacks. I'll get to them next unless there is a general desire not > to see them. If these are packages which have been in wide use, it would be helpful to have input from actual users. I find that far more convincing than input from designers :-) I think that this should also apply for example to GNAT packages and pragmas and attributes. It is not ACT's job to promote these, but it might be appropriate for users of GNAT Pro to argue for adoption of specific features :-) **************************************************************** From: Robert I. Eachus Sent: Sunday, November 10, 2002 6:42 PM There has been a lot of water over the dam in the last few days on this subject, but let's not lose the baby with the bath water. There is an operation on bit arrays which is very common, and some types of software do quite a lot. That is exactly the First/Min operation discussed here. You need to find the first set bit in a one or two dimensional bit array, and do it fast. Some instruction set architectures have a find first bit instruction or a test and set instruction that can significantly improve these operations. For example, in the LALR parser generator on Multics, this operation was needed in the optimization of the state tables. A two dimensional array of from and to states was created, and optimizations were done such as removing all unreachable states. (Not as silly as it seems, earlier optimizations would combine identical states and bypass "empty" productions with no asociated actions.) Initially the array was stored one bit per byte, and the performance was reasonable. But then the size of the tables outgrew a single one megabyte Multics segment. Adding multisegment support slowed things down seriously. Using native Multics instructions to pack and unpack bits was even slower. So Pat Prange and I rewrote the bit search code to use the translate and test character manipulating instruction. This instruction looks each byte up in a table, stops when it finds the first non-zero table entry, and returns the offset in the array and table entry. Of course, we created a table that had the first bit offset for the corresponding byte, and it was trivial (one more instruction) to generate the actual index of the first set bit. This code improved the throughput for this pass by a factor of ten over the original implementation. Not just the bit searches, but the performance of the entire pass, in part because it allowed us to use less memory. (You can do something similar on x86 with REPZ SCAS, but the termination conditions are messier to sort out.) Another frequently needed operation on bit vectors is the population count. The AMD Athlon Processor Code Optimization Guide spends several pages, starting at page 136, on how to do an efficient population count either of 32-bit words or of 64-bit MMX values. (The same code will work on a Pentium 4, but I don't know if the 32-bit code is the most efficient there due to its relatively slow integer shifts. I just happen to have an Athlon XP that I use for number and bit crunching.) See http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/22007.pdf So for me, a standard package that added these operations for bit vectors would be a big win. Other than that the syntactic sugar that converts a bit vector into a set is a total waste of time, at least for me. Serious bit vector operations require these two operations, and it is a pain to have to use low-level programming to do them right. **************************************************************** From: Robert Dewar Sent: Sunday, November 10, 2002 7:35 PM That sounds reasonable indeed, a well designed generic that would provide some general searching capabilities would be useful. **************************************************************** From: Robert I. Eachus Sent: Monday, November 11, 2002 2:24 PM Here is a strawman. The package name should be Ada.something or other, and I don't know if providing Find_Last is a good idea. As far as I am concerned, it is a lot of work for something that most hardware can't do all that well. You could of course, provide a trivial implementation that really performed badly if you want. Oh, and I'd love to be able to require the objects of the type to be padded to 32 or 64 bits, but Ada doesn't have a "round the size of this type up to the correct multiple of 'Alignment, and kick anyone who tries to put it in a smaller component" pragma. ;-) I would love to be able to say "for Set'Size use mod 64;" If people like it, I'll provide an implementation. Warning, warning--MY implementation is correct Ada, but it was written to be efficient on an Athlon XP. Your milage may vary. (In particular, I have several 1K byte tables that fit easily in the L1 data cache on an Athlon.) generic type Element is (<>); package Set_Operations is type Set is array (Element) of Boolean; -- and, or and other operations are implicitly defined. function Find_First(S: in Set) return Element; function Find_Next(S: in Set; E: in Element) return Element; --return the next set Element after E. Needed for iterators, so might as --well make it visible. function Find_Last(S: in Set) return Element; --provided for completeness, but will normally be harder to implement, or --much slower than find first. function Count(S:in Set) return Natural; --returns the number of set bits in S. Empty: exception; -- users should use Count instead of "reading off the end." generic with procedure Operate(E: in Element); procedure Iterate(S: in Set); private -- implementation defined, used to insure that Sets are properly aligned. for Set'Alignment use 8; end Set_Operations; **************************************************************** From: Jeffrey Carter Sent: Friday, November 8, 2002 9:36 PM !wording A.X.13 Unprotected Unbounded Stack Handling The language-defined package Ada.Data_Structures.Stack_Unbounded_Unprotected provides a generic package each of whose instances yields a limited private type Handle and a set of operations. An object of a particular Queue_Unbounded_Unprotected Handle type represents a stack whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Data_Structures.Stack_Unbounded_Unprotected has the following declaration: generic -- Ada.Data_Structures.Stack_Unbounded_Unprotected type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Data_Structures.Stack_Unbounded_Unprotected is pragma Preelaborate; type Handle is limited private; procedure Clear (Stack : in out Handle); procedure Assign (To : out Handle; From : in Handle); procedure Push (Onto : in out Handle; Item : in Element); procedure Pop (From : in out Handle; Item : out Element); function Length (Stack : Handle) return Natural; function Is_Empty (Stack : Handle) return Boolean; function Peek (Stack : Handle) return Element; generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Handle; Context : in out Context_Data); private -- Ada.Data_Structures.Stack_Unbounded_Unprotected -- not specified by the language end Ada.Data_Structures.Stack_Unbounded_Unprotected; A stack is an ordered collection of Elements. Elements are added to and removed from the top of a stack. A stack is a last in, first out (LIFO) data structure. An object of type Handle is initially empty. procedure Clear (Stack : in out Handle); Clear makes Stack empty. Postcondition: Is_Empty (Stack) Length (Stack) = 0 procedure Assign (To : out Handle; From : in Handle); If To and From are the same Stack, Assign has no effect. Otherwise, To is cleared and made into a copy of From. If, after clearing To, there is insufficient storage for copies of all the Elements in From, Storage_Exhaustion_Error is propagated. Postcondition: Length (To) = Length (From) procedure Push (Onto : in out Handle; Item : in Element); If there is insufficient storage available to store Item in Onto, Storage_Exhaustion_Error is propagated. Otherwise, Put adds Item to the top of Onto. Postcondition: not Is_Empty (Onto) Length (Onto) > 0 procedure Pop (From : in out Handle; Item : out Element); If From is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, the Element on the top of From is removed from From and put in Item. Precondition: not Is_Empty (From) function Length (Stack : Handle) return Natural; Length returns the number of Elements stored in Stack. function Is_Empty (Stack : Handle) return Boolean; Is_Empty returns True if Stack is empty and False otherwise. function Peek (Stack : Handle) return Element; If Stack is empty, Ada.Data_Structures.Empty_Structure_Error is propagated. Otherwise, Peek returns the Element on the top of Stack without altering Stack. Precondition: not Is_Empty (Stack) generic -- Iterate type Context_Data (<>) is limited private; with procedure Action (Item : in out Element; Context : in out Context_Data; Continue : out Boolean); procedure Iterate (Over : in out Handle; Context : in out Context_Data); An instance of this procedure applies Action to each Element stored in Over in turn, from top to bottom. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in Over. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the queue. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a stack while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. AARM Note This package could be implemented using an instantiation of Ada.Data_Structures.List_Unbounded_Unprotected. !discussion The reference implementation of Ada.Data_Structures.Stack_Unbounded_Unprotected is PragmARC.Stack_Unbounded_Unprotected; it differs in its name and the names of exceptions. Type Handle is a wrapper around a List_Unbounded_Unprotected Handle in the reference implementation. !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a stack. Once the entire file has been read, output the number of values read followed by all the values, in reverse order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Stack is new Ada.Data_Structures.Stack_Unbounded_Unprotected (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; procedure Put_All is new Integer_Stack.Iterate (Context_Data => Boolean, Action => Put); I : Integer; Dummy : Boolean := False; Stack : Integer_Stack.Handle; use Integer_Stack; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Push (Item => I, Onto => Queue); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Length (Stack) ) ); Put_All (Over => Stack, Context => Dummy); end; **************************************************************** From: Robert Dewar Sent: Saturday, November 9, 2002 5:24 PM I must say I find this continued insistence on the misuse of the term Handle (which to me must imply reference semantics) disturbing. This mistake would be enough for me to reject any package using this approach. it is simply too confusing. **************************************************************** From: Jeffrey Carter Sent: Thursday, November 14, 2002 1:43 PM !wording A.X.14 Protected Bounded List Handling The language-defined package Ada.Data_Structures.List_Bounded provides a generic package each of whose instances yields a protected type Handle with a set of operations. An object of a particular List_Bounded_Protected Handle type represents a list whose size can vary conceptually between 0 and a maximum size established at the declaration of the object. The client must also specify the ceiling priority of the protected object at its declaration. Static Semantics The library package Ada.Data_Structures.List_Bounded has the following declaration: with System; generic -- Ada.Data_Structures.List_Bounded type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Data_Structures.List_Bounded is pragma Preelaborate; type Position is private; Invalid_Position : exception; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Pos : in Position; Continue : out Boolean); protected type Handle (Max_Size : Positive; Ceiling_Priority : System.Any_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; function First return Position; function Last return Position; function Off_List return Position; function Next (Pos : Position) return Position; function Prev (Pos : Position) return Position; procedure Insert (Item : in Element; Before : in Position; New_Pos : out Position); procedure Append (Item : in Element; After : in Position; New_Pos : out Position); procedure Delete (Pos : in out Position); function Get (Pos : Position) return Element; procedure Put (Pos : in Position; Item : in Element); function Is_Empty return Boolean; function Is_Full return Boolean; function Length return Natural; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Handle -- not specified by the language end Handle; private -- Ada.Data_Structures.List_Bounded -- not specified by the language end Ada.Data_Structures.List_Bounded; An object of type Handle represents the same abstraction as an object of type Ada.Data_Structures.List_Bounded_Unprotected.Handle, but protected to allow access by multiple tasks. An object of type Handle is initially empty. An object of type Position is initially invalid. procedure Clear; Clear makes the list empty. Clear effectively deletes all nodes in a list. Therefore, Position_Error is propagated if a Position, obtained before Clear is called, is used to access the list after Clear is called. Postcondition: Is_Empty function First return Position; First returns a Position indicating the first Element in the list. If the list is empty, this is the same as Off_List. function Last return Position; Last returns a Position indicating the last Element in the list. If the list is empty, this is the same as Off_List. function Off_List return Position; Off_List returns a Position that is valid for the list but indicates no Element in the list. This is the same Position returned by Next (Last) and by Prev (First). function Next (Pos : Position) return Position; If Pos is not a valid Position for the list, Position_Error is propagated. If Pos = Last, Next returns Off_List. If Pos = Off_List, Next returns First. Otherwise, Next returns a Position that indicates the next Element in the list after the Element indicated by Pos. function Prev (Pos : Position) return Position; If Pos is not a valid Position for the list, Position_Error is propagated. If Pos = First, Prev returns Off_List. If Pos = Off_List, Prev returns Last. Otherwise, Prev returns a Position that indicates the previous Element in the list before the Element indicated by Pos. procedure Insert (Item : in Element; Before : in Position; New_Pos : out Position); If the list is full, Something.Full_Structure_Error is propagated. If Before is not a valid Position for the list, Position_Error is propagated. If the list is empty and Before /= Off_List, Position_Error is propagated. If Before = Off_List, Item is added as the last Element in the list. Otherwise, Item is inserted into the list before the Element indicated by Before. New_Pos becomes a valid Position for the list that indicates Item. Precondition: not Is_Full Postcondition: Prev (Before) = New_Pos procedure Append (Item : in Element; After : in Position; New_Pos : out Position); If the list is full, Something.Full_Structure_Error is propagated. If After is not a valid Position for the list, Position_Error is propagated. If the list is empty and After /= Off_List, Position_Error is propagated. If After = Off_List, Item is added as the first Element in the list. Otherwise, Item is inserted into the list after the Element indicated by After. New_Pos becomes a valid Position for the list that indicates Item. Precondition: not Is_Full Postcondition: Next (After) = New_Pos procedure Delete (Pos : in out Position); If the list is empty, Something.Empty_Structure_Error is propagated. If Pos is not a valid Position for the list or Pos = Off_List, Position_Error is propagated. Otherwise, the Element indicated by Pos is removed from the list. Pos is made invalid. Elements are stored in "nodes" in a list. A Position indicates an Element by indicating its node. One Position may indicate a node that has been deleted through another Position. Position_Error is propagated if such a Position is subsequently used to access the list. Precondition: not Is_Empty Postcondition: not Is_Full function Get (Pos : Position) return Element; If the list is empty, Something.Empty_Structure_Error is propagated. If Pos is not a valid Position for the list or Pos = Off_List, Position_Error is propagated. Otherwise, Get returns the Element in the list indicated by Pos. Precondition: not Is_Empty procedure Put (Pos : in Position; Item : in Element); If the list is empty, Something.Empty_Structure_Error is propagated. If Pos is not a valid Position for the list or Pos = Off_List, Position_Error is propagated. Otherwise, the Element in the list indicated by Pos is made to be Item. Precondition: not Is_Empty function Is_Empty return Boolean; Is_Empty returns True if the list is empty and False otherwise. function Is_Full return Boolean; Is_Full returns True if the list is full and False otherwise. function Length return Natural; Length returns the number of Elements stored in the list. procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the list and its Position in turn, from first to last. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the list. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the list. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a list while iterating over it. Possible consequences are: propagating Position_Error, propagating Constraint_Error, not processing some of the Elements stored in the list, processing some of the Elements twice, and normal processing. Implementation Permissions An implementation may reuse deleted nodes, even if that means it cannot guarantee that Position_Error will be propagated when a Position that indicates a deleted node is used to access the list. An implementation may add additional declarations to the specification of Ada.Data_Structures.List_Bounded as needed to complete the private part of protected type Handle. AARM Note This package could be implemented using an instantiation of Ada.Data_Structures.List_Bounded_Unprotected. !discussion The reference version of Ada.Data_Structures.List_Bounded is PragmARC.List_Bounded. It differs in its name and the names of exceptions. It contains a visible instantiation of PragmARC.List_Bounded_Unprotected named Implementation; the private part of Handle is List : Implementation.Handle; Type Position is implemented as a type derived from Implementation.Position. !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains 1,000 or fewer integers, one per line, read all of the values in Input and store them in a list in the order read. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_List is new Something.List_Bounded (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Pos : in Integer_List.Position; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Boolean := False; List : Integer_List.Handle (Max_Size => 1_000, Ceiling_Priority => System.Default_Priority); Pos : Integer_List.Position; Off_List : constant Integer_List.Position := List.Off_List; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); List.Insert (Before => Off_List, Item => I, New_Pos => Pos); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (List.Length) ); List.Iterate (Action => Put'access, Context => Dummy); end; **************************************************************** From: Jeffrey Carter Sent: Thursday, November 21, 2002 3:36 PM !wording A.X.15 Protected Unbounded List Handling The language-defined package Ada.Data_Structures.List_Unbounded provides a generic package each of whose instances yields a protected type Handle with a set of operations. An object of a particular List_Unbounded_Protected Handle type represents a list whose size can vary conceptually between 0 and a maximum size established established by available storage. Static Semantics The library package Ada.Data_Structures.List_Unbounded has the following declaration: with System; generic -- Ada.Data_Structures.List_Unbounded type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Data_Structures.List_Unbounded is pragma Preelaborate; type Position is private; Invalid_Position : exception; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Pos : in Position; Continue : out Boolean); protected type Handle (Ceiling_Priority : System.Any_Priority := System.Default_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; function First return Position; function Last return Position; function Off_List return Position; function Next (Pos : Position) return Position; function Prev (Pos : Position) return Position; procedure Insert (Item : in Element; Before : in Position; New_Pos : out Position); procedure Append (Item : in Element; After : in Position; New_Pos : out Position); procedure Delete (Pos : in out Position); function Get (Pos : Position) return Element; procedure Put (Pos : in Position; Item : in Element); function Is_Empty return Boolean; function Length return Natural; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Handle -- not specified by the language end Handle; private -- Ada.Data_Structures.List_Unbounded -- not specified by the language end Ada.Data_Structures.List_Unbounded; procedure Clear; Clear makes the list empty. Clear effectively deletes all nodes in a list. Therefore, Position_Error is propagated if a Position, obtained before Clear is called, is used to access the list after Clear is called. Postcondition: Is_Empty function First return Position; First returns a Position indicating the first Element in the list. If the list is empty, this is the same as Off_List. function Last return Position; Last returns a Position indicating the last Element in the list. If the list is empty, this is the same as Off_List. function Off_List return Position; Off_List returns a Position that is valid for the list but indicates no Element in the list. This is the same Position returned by Next (Last) and by Prev (First). function Next (Pos : Position) return Position; If Pos is not a valid Position for the list, Position_Error is propagated. If Pos = Last, Next returns Off_List. If Pos = Off_List, Next returns First. Otherwise, Next returns a Position that indicates the next Element in the list after the Element indicated by Pos. function Prev (Pos : Position) return Position; If Pos is not a valid Position for the list, Position_Error is propagated. If Pos = First, Prev returns Off_List. If Pos = Off_List, Prev returns Last. Otherwise, Prev returns a Position that indicates the previous Element in the list before the Element indicated by Pos. procedure Insert (Item : in Element; Before : in Position; New_Pos : out Position); If there is insufficient storage available to store Item in the list, Storage_Exhaustion_Error is propagated. If Before is not a valid Position for the list, Position_Error is propagated. If the list is empty and Before /= Off_List, Position_Error is propagated. If Before = Off_List, Item is added as the last Element in the list. Otherwise, Item is inserted into the list before the Element indicated by Before. New_Pos becomes a valid Position for the list that indicates Item. Postcondition: Prev (Before) = New_Pos procedure Append (Item : in Element; After : in Position; New_Pos : out Position); If there is insufficient storage available to store Item in the list, Storage_Exhaustion_Error is propagated. If After is not a valid Position for the list, Position_Error is propagated. If the list is empty and After /= Off_List, Position_Error is propagated. If After = Off_List, Item is added as the first Element in the list. Otherwise, Item is inserted into the list after the Element indicated by After. New_Pos becomes a valid Position for the list that indicates Item. Postcondition: Next (After) = New_Pos procedure Delete (Pos : in out Position); If the list is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for the list or Pos = Off_List, Position_Error is propagated. Otherwise, the Element indicated by Pos is removed from the list. Pos is made invalid. Elements are stored in "nodes" in a list. A Position indicates an Element by indicating its node. One Position may indicate a node that has been deleted through another Position. Position_Error is propagated if such a Position is subsequently used to access the list. Precondition: not Is_Empty function Get (Pos : Position) return Element; If the list is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for the list or Pos = Off_List, Position_Error is propagated. Otherwise, Get returns the Element in the list indicated by Pos. Precondition: not Is_Empty procedure Put (Pos : in Position; Item : in Element); If the list is empty, Empty_Structure_Error is propagated. If Pos is not a valid Position for the list or Pos = Off_List, Position_Error is propagated. Otherwise, the Element in the list indicated by Pos is made to be Item. Precondition: not Is_Empty function Is_Empty return Boolean; Is_Empty returns True if the list is empty and False otherwise. function Length return Natural; Length returns the number of Elements stored in the list. procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the list and its Position in turn, from first to last. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the list. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the list. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a list while iterating over it. Possible consequences are: propagating Position_Error, propagating Constraint_Error, not processing some of the Elements stored in the list, processing some of the Elements twice, and normal processing. Implementation Requirements No storage associated with a Handle object shall be lost upon scope exit or a call to Clear or Delete. Implementation Permissions An implementation may reuse deleted nodes, even if that means it cannot guarantee that Position_Error will be propagated when a Position that indicates a deleted node is used to access the list. An implementation may add additional declarations to the specification of Ada.Data_Structures.List_Unbounded as needed to complete the private part of protected type Handle. AARM Note This package could be implemented using an instantiation of Ada.Data_Structures.List_Unbounded_Unprotected. !discussion The reference version of Ada.Data_Structures.List_Unbounded is PragmARC.List_Unbounded. It differs in its name and the names of exceptions, and contains a sort operation not provided here. It contains a visible instantiation of PragmARC.List_Unbounded_Unprotected named Implementation; the private part of Handle is List : Implementation.Handle; Type Position is implemented as a type derived from Implementation.Position. !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a list in the order read. Once the entire file has been read, output the number of values read followed by all the values, in order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_List is new Ada.Data_Structures.List_Unbounded (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Pos : in Integer_List.Position; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Boolean := False; List : Integer_List.Handle; Pos : Integer_List.Position; Off_List : constant Integer_List.Position := List.Off_List; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); List.Insert (Before => Off_List, Item => I, New_Pos => Pos); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (List.Length) ); List.Iterate (Action => Put'access, Context => Dummy); end; **************************************************************** From: Jeffrey Carter Sent: Tuesday, November 26, 2002 3:24 PM !wording A.X.16 Protected Unbounded Bag Handling The language-defined package Ada.Data_Structures.Bag_Unbounded provides a generic package each of whose instances yields a protected type Handle with a set of operations. An object of a particular Bag_Unbounded Handle type represents a list whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Data_Structures.Bag_Unbounded has the following declaration: with System; generic -- Ada.Data_Structures.Bag_Unbounded type Element is private; with function "=" (Left : Element; Right : Element) return Boolean is <>; package Ada.Data_Structures.Bag_Unbounded is pragma Preelaborate; type Find_Result (Found : Boolean := False) is record case Found is when False => null; when True => Item : Element; end case; end record; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Continue : out Boolean); protected type Handle (Ceiling_Priority : System.Any_Priority := System.Default_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; procedure Add (Item : in Element); procedure Delete (Item : in Element); procedure Update (Item : in Element); function Find (Key : Element) return Find_Result; function Empty return Boolean; function Size return Natural; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Handle -- not specified by the language end Handle; end Ada.Data_Structures.Bag_Unbounded; procedure Clear; Clear makes the bag empty. Postcondition: Is_Empty Size = 0 procedure Add (Item : in Element); If there is insufficient storage available to store Item in the bag, Storage_Exhaustion_Error is propagated. Otherwise, Add adds Item to the bag. Postcondition: not Is_Empty procedure Delete (Item : in Element); If the bag contains an Element X such that X = Item, Delete deletes X from the bag. Otherwise, Delete has no effect. If the bag contains more than one such Element, Delete deletes one of these Elements. Which of these Elements is deleted is undefined. procedure Update (Item : in Element); If the bag contains an Element X such that X = Item, Update conceptually performs X := Item. Otherwise, Update has no effect. If the bag contains more than one such Element, Update updates one of these Elements. Which of these Elements is updated is undefined. Type Find_Result defines the results of function Find. function Find (Key : Element) return Find_Result; If the bag contains an Element X such that X = Key, Find returns (Found => True, Item => X). Otherwise, Find returns (Found => False). If the bag contains more than one such Element, Find returns one of these Elements as the Item component of the result. Which of these Elements is returned is undefined. function Empty return Boolean; Is_Empty returns True if the bag contains no elements and False otherwise. function Size return Natural; Size returns the number of elements stored in the bag. procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the bag. The order in which the Elements in the bag are processed is undefined. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the bag. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the bag. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a bag while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in Over, processing some of the Elements twice, and normal processing. Implementation Requirements No storage associated with a Handle object shall be lost upon scope exit or a call to Clear or Delete. Implementation Permissions An implementation may add additional declarations to the specification of Ada.Data_Structures.Bag_Unbounded as needed to complete the private part of protected type Handle. AARM Note This package could be implemented using an instantiation of Ada.Data_Structures.Bag_Unbounded_Unprotected. !discussion The reference version of Ada.Data_Structures.Bag_Unbounded is PragmARC.Bag_Unbounded. It differs in its name and the names of exceptions. It contains a visible instantiation of PragmARC.Bag_Unbounded_Unprotected named Implementation; the private part of Handle is Bag : Implementation.Handle; !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a bag. Once the entire file has been read, output the number of values read followed by all the values to the standard output. declare package Integer_Bag is new Ada.Data_Structures.Bag_Unbounded (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Boolean := False; Bag : Integer_Bag.Handle; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Bag.Add (Item => I); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Bag.Size) ); Bag.Iterate (Action => Put'access, Context => Dummy); end; **************************************************************** From: Jeffrey Carter Sent: Tuesday, December 3, 2002 8:52 PM !wording A.X.17 Protected Unbounded Stack Handling The language-defined package Ada.Data_Structures.Stack_Unbounded provides a generic package each of whose instances yields a protected type Handle with a set of operations. An object of a particular Stack_Unbounded Handle type represents a stack whose size can vary conceptually between 0 and a maximum size established by available storage. Static Semantics The library package Ada.Data_Structures.Stack_Unbounded has the following declaration: with System; generic -- Ada.Data_Structures.Stack_Unbounded type Element is limited private; with procedure Assign (To : out Element; From : in Element) is <>; package Ada.Data_Structures.Stack_Unbounded is pragma Preelaborate; type Context_Data is tagged null record; type Action_Ptr is access procedure (Item : in out Element; Context : in out Context_Data'Class; Continue : out Boolean); protected type Handle (Ceiling_Priority : System.Any_Priority := System.Default_Priority) is pragma Priority (Ceiling_Priority); procedure Clear; procedure Push (Item : in Element); procedure Pop (Item : out Element); function Is_Empty return Boolean; function Length return Natural; function Peek return Element; procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); private -- Handle -- not specified by the language end Handle; end Ada.Data_Structures.Stack_Unbounded; procedure Clear; Clear makes the stack empty. Postcondition: Is_Empty Length = 0 procedure Push (Item : in Element); If there is insufficient storage available to store Item in the stack, Storage_Exhaustion_Error is propagated. Otherwise, Push adds Item to the top of the stack. Postcondition: not Is_Empty procedure Pop (Item : out Element); If the stack is empty, Empty_Structure_Error is propagated. Otherwise, the Element on the top of the stack is removed from the stack and put in Item. Precondition: not Is_Empty function Is_Empty return Boolean; Is_Empty returns True if the stack contains no elements and False otherwise. function Length return Natural; Length returns the number of elements stored in the stack. function Peek return Element; If the stack is empty, Empty_Structure_Error is propagated. Otherwise, Peek returns the Element on the top of the stack without altering the stack. Precondition: not Is_Empty procedure Iterate (Action : in Action_Ptr; Context : in out Context_Data'Class); Iterate applies Action to each Element stored in the stack in turn, from top to bottom. If Action sets Continue to False, this procedure returns immediately, without processing any remaining Elements in the the stack. If Action propagates an exception, Iterate immediately propagates that exception, without processing any remaining Elements in the stack. Context is passed to Action to represent any context data that Action needs to access or modify. It is a bounded error to modify a stack while iterating over it. Possible consequences are: propagating Constraint_Error, propagating an exception not visible to the client, not processing some of the Elements stored in the stack, processing some of the Elements twice, and normal processing. Implementation Requirements No storage associated with a Handle object shall be lost upon scope exit or a call to Clear or Delete. Implementation Permissions An implementation may add additional declarations to the specification of Ada.Data_Structures.Stack_Unbounded as needed to complete the private part of protected type Handle. AARM Note This package could be implemented using an instantiation of Ada.Data_Structures.Stack_Unbounded_Unprotected. !discussion The reference version of Ada.Data_Structures.Stack_Unbounded is PragmARC.Stack_Unbounded. It differs in its name and the names of exceptions. It contains a visible instantiation of PragmARC.Stack_Unbounded_Unprotected named Implementation; the private part of Handle is Stack : Implementation.Handle; !example Given that Input is an Ada.Text_IO.File_Type that has been opened and contains zero or more integers, one per line, read all of the values in Input and store them in a stack. Once the entire file has been read, output the number of values read followed by all the values, in reverse order, to the standard output. declare procedure Assign (To : out Integer; From : in Integer) is -- null; begin -- Assign To := From; end Assign; package Integer_Stack is new Ada.Data_Structures.Stack_Unbounded (Element => Integer); procedure Put (I : in out Integer; Context : in out Boolean; Continue : out Boolean) is -- null; begin -- Put Ada.Text_IO.Put_Line (Item => Integer'Image (I) ); Continue := True; end Put; I : Integer; Dummy : Boolean := False; Stack : Integer_Stack.Handle; begin Read_All : loop exit Read_All when Ada.Text_IO.End_Of_File (Input); Ada.Integer_Text_IO.Get (File => Input, Item => I); Stack.Push (Item => I); end loop Read_All; Ada.Text_IO.Put_Line (Item => "Number of values read is" & Integer'Image (Stack.Length) ); Stack.Iterate (Action => Put'access, Context => Dummy); end; **************************************************************** From: Jeffrey Carter Sent: Wednesday, December 4, 2002 7:54 PM !wording A.X.18 Assign Procedure Creation The language-defined procedure Ada.Data_Structures.Assignment provides a generic procedure each of whose instances yields a procedure suitable for the actual parameter associated with the generic formal Assign procedure required by many of the data structure packages. Static Semantics The library procedure Ada.Data_Structures.Assignment has the following declaration: generic -- Ada.Data_Structures.Assignment type Element (<>) is private; procedure Ada.Data_Structures.Assignment (To : out Element; From : in Element); pragma Pure (Ada.Data_Structures.Assignment); An instance of Assignment performs To := From; In many cases, the desired actual Assign procedure for instantiating a data structure package is simply the predefined assignment operation. This procedure is provided as a convenience for creating such procedures. !example Instantiate Stack_Unbounded for the predefined type Float. procedure Assign is new Ada.Data_Structures.Assignment (Element => Float); package Float_Stack is new Ada.Data_Structures.Stack_Unbounded (Element => Float); **************************************************************** From: Jeffrey Carter Sent: Thursday, December 12, 2002 7:45 PM With the update to AI-302 for Ada.Data_Structures.Assignment, the updates are finished. My proposal is now complete (at least in its initial form). There was a great deal of cut-and-paste reuse in creating the proposal. We all know the kinds of errors that leads to. Please report any errors you encounter. The call for APIs indicates that they would be part of a secondary standard, so I'm a bit concerned about wording this as a subsection of ARM A. There have been a number of suggestions for changes to the proposal. Some seem like good ideas, and some seem to have widespread support. However, the call for APIs indicates that preference will be given to APIs based on existing components with a reference implementation and user base. This proposal is based on the PragmARCs (with some minor changes suggested by Randy). Incorporating the proposed changes would mean the proposal is no longer based on existing components. So I'm somewhat conflicted about what to do here, too. I held off on presenting the protected list, bag, and stack components until the end because it's questionable how useful and necessary they are, and if anything intervened to make me leave some components out I wanted to leave out these. It might be a good idea to renumber the sections to put all the components of the same type together. **************************************************************** From: Robert A. Duff Sent: Thursday, December 12, 2002 8:46 PM > There have been a number of suggestions for changes to the proposal. > Some seem like good ideas, and some seem to have widespread support. Is there any overlap between these two classes? ;-) **************************************************************** From: Jeffrey Carter Sent: Friday, December 13, 2002 2:46 PM > Is there any overlap between these two classes? Surprisingly, yes. > > ;-) Ditto. **************************************************************** From: Randy Brukardt Sent: Friday, December 13, 2002 2:59 PM > Surprisingly, yes. Would you care to enumerate them? I'd be surprised if the ARG didn't tinker with the packages submitted (no matter from what source), so it would be good to see what ideas appear to have consensus support. Indeed, it probably would be valuable to see the proposal with those ideas integrated, since it seems unlikely that we would want to adopt something without them. (Unless, of course, the consensus is illusory). (The AIs are under version control, after all. If someone want to see the original proposal, they can pull it up on the web site.) **************************************************************** From: Jeffrey Carter Sent: Saturday, December 14, 2002 12:08 PM This will probably have to wait until I return from my trip to England next year. **************************************************************** From: Michael Erdmann Sent: Wednesday, January 1, 2003 10:37 AM When i am using bags in the past, i found it very usefull to have an toArray operation, which creates a copy of the bag in an array of the Element type. Any chance to add this? **************************************************************** From: Matthew Heaney Sent: Friday, January 3, 2003 9:15 AM Technically, you shouldn't need such an operation, if you have some form of iterator. For example, given a passive iterator: declare A : array (1 .. Length (Set)) of Item_Type; I : Positive := 1; procedure Process (Item : Item_Type) is begin A (I) := Item; I := I + 1; end; procedure Copy is new Generic_Iteration (Process); begin Copy (Set); end; The problem with your request is that you don't what to hardcode the array type -- the client should be able to supply his own. But then again, you could have a custom op that imports an array type as a generic formal type. But then again, you run the risk of having too many special-purpose ops. The algorithm I showed above can be used for *any* target type -- it doesn't have to be an array. If this is something you do often (copy a set into an array), then you could always build a separate, stand-alone generic subprogram to do it, that you can reuse as you wish, e.g. with Generic_Multisets; generic with package P is new Generic_Multisets (<>); type Index_Type is (<>); type Array_Type is array (Index_Type range <>) of P.Item_Type; procedure Copy_From_Multiset_To_Array (Source : in P.Set_Type; Target : out Array_Type; Last : out Index_Type'Base); Will something like that work? **************************************************************** From: Michael Erdmann Sent: Friday, January 3, 2003 12:33 PM I know that i can do this always on my own. Since the bag implementation proably knows better how to access the bag internals in a fast way, it is verry likely that an package internal implementation is mutch more efficient then a procedure i would build on top of the iterator interface. You are right, i would be nice is the package could provide the array type. I forgot to mention that from my point of view the order of the elements in the array may be unspecified. **************************************************************** From: Jeffrey Carter Sent: Wednesday, January 8, 2003 6:20 PM Good ideas with widespread support among the ARG are now being reviewed. If this is such an idea it would probably apply equally to all the components. **************************************************************** From: Jeffrey Carter Sent: Thursday, January 9, 2003 7:48 PM > Would you care to enumerate them? > > I'd be surprised if the ARG didn't tinker with the packages submitted (no > matter from what source), so it would be good to see what ideas appear to > have consensus support. OK, here's a first cut: Package names: Add Ada.Data_Structures.Lists, containing the declaration of exception Position_Error Under Lists have packages Bounded_Unprotected, Bounded, Unbounded_Unprotected, and Unbounded [or maybe Bounded, Bounded_Protected, Unbounded, and Unbounded_Protected] Change Set_Discrete to Bit_Mapped_Sets List handling: Change Append to always add at the end, eliminate After. Keep Insert as is Type names: Change Handle to something else Bounded_List [Bag, Queue, Stack] Unbounded_List [Bag, Queue, Stack] Skip_List Bit_Mapped_Set Add Implementation Advice saying Bit_Mapped_Set should be implemented as an array of Boolean. **************************************************************** From: Pascal Obry Sent: Friday, January 10, 2003 1:07 AM I would prefer Ada.Containers here. It is shorter and looks better to me. Of course this is ok only if there is only containers into the children packages. It seems the case at the moment, maybe you plan to add some more stuff later... **************************************************************** From: Marc A. Criley Sent: Friday, January 10, 2003 7:58 PM I would second using "Containers" rather than "Data_Structures". It's more concise, equally expressive, and more in tune with industry nomenclature. **************************************************************** From: Jeffrey Carter Sent: Saturday, February 1, 2003 4:54 PM Randy asked to see what these packages would look like with the consensus changes made to them, so here they are. I've used Ada_Data_Structures instead of Ada.Data_Structures and created dummy private parts so I could check these with a compiler. I'll try to change all of those, but if I miss any, please make the change in your mind. The main changes are package names and type names. The Append operation for lists no longer takes a Position parameter and always appends at the end of the list. These are just the package specs. If there's interest in continuing along these lines I'll then proceed with embedding these in the appropriate AI wording, most of which already exists from the previous version. [These make up version /06 of the AI. - ED] **************************************************************** From: Marius Amado Alves Sent: Thursday, September 9, 2003 1:26 PM I submit this document Bases for the Design of a Standard Container Library for Ada Version 2 http://www.liacc.up.pt/~maa/bases_2.txt henceforth "Bases", for appreciation by the ARG, as an item in the standard container library strain of responses to their "call for APIs". Although the call originally stated preference for existing API's, in the ensuing discussions in that strain the desire for higher level items was expressed. The Bases document realises one of such higher level items, namely that of design decisions one step above the complete source-code formulation of the APIs. Still higher level items like general requirements and purpose statements exist informally in the body of knowledge. For these no formal documentation exists yet. However the Bases document has some of those higher level items implicit in its formulations. An envisioned improvement of the Bases is therefore to make those items explicit. That was not done yet simply because of lack of time. Version 1 of the Bases has circulated in CLA and ASCLWG recently. Some comments produced and were incorporated in Version 2. Version 1 is still available at the same address for whatever purposes. * * * Perhaps the hottest Bases issue is the requirement that element types be indefinite. This is hot because the two official API proposals, AI302 and Charles, do not currently meet this requirement. Discussion about this so far has happened in CLA and ASCLWG that I know of. In CLA several people have expressed the desire for indefinite elements. Reasons include supporting class-wide elements, and pointerless programming. The main objection voiced on ASCLWG regarded implementation. But it has been shown that at least one solution exists in pure Ada 95, and anyway the library is not absolutely required to be implemented in pure Ada 95. (Regarding pointerless programming Ada 95 gets in the way big time. When you want a heterogeneous array you want a heterogeneous array not an array of pointers to whatever. With a container library supporting indefinite elements we have a chance of remedying this. The case for pointerless programming is well known: pointers are evil.) **************************************************************** From: Randy Brukardt Sent: Thursday, September 25, 2003 3:44 PM Jeffrey Carter noticed a number of typos in version /06, which have been corrected in version /07. **************************************************************** From: Robert I. Eachus Sent: Wednesday, October 29, 2003 1:37 AM I am beginning to think that that is a bad idea, perhaps beyond actually defining a directory Ada.Containers with no contents. ;-) I have been thinking about this for several days now. The problem is that we know what the "right" way to declare the container types is, but actually implementing the generic packages that result correctly--and without pointers and extra heap allocations--is nearly impossible. I've been putting together some containers of my own, and one the one thing I miss is that I'd like to be able to have a generic formal parameter "type Element(<>) is private" (or limited private), then create a record type with a component of that type, and a couple of other scalars, usually pointers. But in theory, it can't be done! I can actually write the code, and I am doing it, but I am playing fast and loose with Unchecked_Conversion and overlays under the covers. The problem is that, in the example above, I can write Foo: Element := ...; and the type of Foo (if Element is a classwide type) or the bounds of Foo, if it is an unconstrained array type, or the discriminants of Foo, if it is a record type with discriminants will be set to the initial value. But if I want to wrap one of those three in a record, say Element_Record, I can't do it. Is there any reason why this is illegal? No. If it were to be allowed, it would probably make sense to restrict the permission to the last component of a record declaration. (And to allow the record type to have discriminants, but not to have a variant part.) That would be a huge improvement to Ada, but it would mean that any component library adopted as a standard would probably be instantly obsolete. We would end up with a record type that could only appear in formal parameters, return statements, allocators, as the prefix of a component reference, and as the initial value of an object. These records could not appear on the LHS of an assignment statement (except as the prefix of the non-indefinite components). But who cares? The only places that they can occur are exactly the places that you need them--especially in the allocators. The advantage is not that using such records allows you to build data structures without access types. No, it eliminates the need to have two access types where one will do. In what I guess I should call the classic list structure case, you end up with a access value pointing to a record containing an access value and an Element, instead of one access value pointing to a record containing two access values, one linking to the next list access value pair, and one pointing to the Element object. I'm not sure whether it really is worth putting this in Ada0X, but I do think it would be much more useful for the ARG to work on this idea--or something like it, than on one more container library for Ada. **************************************************************** From: Dan Eilers Sent: Wednesday, October 29, 2003 11:31 AM Maybe a slightly different way of looking at this is that you would like to be able to create a new indefinite type by "extending" an existing indefinite type, by adding some non-indefinite components. I put "extending" in quotes, because you want the added fields to actually go at the front, since you aren't concerned with polymorphism/substitutability associated with extending tagged types. By looking at it as extending a type rather than creating a new record type, you wouldn't need the funky rules about no variant part, and only one indefinite component in the record, and the indefinite component needing to be last. You would also gain the convenience of being able to refer to the indefinite part of the record directly, rather than as a field of an enclosing record. This would for example allow directly taking a slice of a user-defined unbounded string type, which isn't currently allowed. ****************************************************************