Version 1.1 of ai05s/ai05-0069-1.txt

Unformatted version of ai05s/ai05-0069-1.txt version 1.1
Other versions for file ai05s/ai05-0069-1.txt

!standard A.18(0/2)          07-10-24 AI05-0069-1/01
!class Amendment 07-10-24
!status work item 07-10-24
!status received 07-10-02
!priority Medium
!difficulty Hard
!subject Singleton container
!summary
(See proposal.)
!problem
It not possible in Ada to declare an object of an indefinite type that can hold any value of that type -- the language requires an initialization value and for the object to be constrained by that value. Worse, components of indefinite types are illegal.
However, it is often useful to have objects and components of indefinite types. One example is the classwide type at the root of a tree of types. It's not uncommon to have operations that produce values of such a type, and to want to store those values as components of a type.
This can be done using access types and explicit storage management. However, this is error-prone. Moreover, we've defined containers to eliminate the need for explicit memory management of common data structures.
Thus it is appropriate to have have a container to handle this operation.
!proposal
Define a singleton container as follows:
generic type Element_Type (<>) is private; package Ada.Containers.Holders is -- This package provides a "holder" of a definite type that contains -- a single value of an indefinite type. -- This allows one to effectively declare an uninitialized variable -- or component of an indefinite type.
type Holder is tagged private;
function To_Holder (New_Item : Element_Type) return Holder; -- Returns a non-empty holder containing an element initialized to New_Item.
function Is_Empty (Container : Holder) return Boolean; -- Returns True if the holder is empty, and -- False if it contains an element.
procedure Clear (Container : Holder); -- Removes the element from Container.
function Element (Container : Holder) return Element_Type; -- If Container is empty, Constraint_Error is propagated. -- Otherwise, returns the element stored in Container.
procedure Replace_Element (Container : in out Holder; New_Item : in Element_Type); -- Replace_Element assigns the value New_Item into Container, replacing -- any preexisting content of Container. Container is not empty -- after a successful call to Replace_Element.
procedure Query_Element (Container : in Holder; Process : not null access procedure (Element : in Element_Type)); -- If Container is empty, Constraint_Error is propagated. -- Otherwise, Query_Element calls Process.all with the contained element as -- the argument. Program_Error is raised if Process.all tampers with the elements -- of Container. Any exception raised by Process.all is propagated.
procedure Update_Element (Container : in Holder; Process : not null access procedure (Element : in out Element_Type)); -- If Container is empty, Constraint_Error is propagated. -- Otherwise, Query_Element calls Process.all with the contained element as -- the argument. Program_Error is raised if Process.all tampers with the elements -- of Container. Any exception raised by Process.all is propagated.
procedure Move (Target : in out Holder; Source : in out Holder); -- If Target denotes the same object as Source, then Move has no effect. -- Otherwise, the element contained by Source (if any) is removed from Source -- and inserted into Target, replacing any preexisting content. Source is empty -- after a successful call to Move.
private -- ... Not specified by the language. end Ada.Containers.Holders;
!wording
** TBD ** (Most of the needed wording is given as comments above; the definition of tampering is given below.)
A subprogram is said to tamper with elements of a holder object H if: * It clears the element contained by H, that is, it calls the Clear procedure with H
as a parameter;
* It replaces the element contained by H, that is, it calls the Replace_Element
procedure with H as a parameter;
* It calls the Move procedure with H as a parameter; * It finalizes H.
!discussion
Note that it isn't necessary to define a definite version of this container; that would just be a complicated way to write "A : Element_Type".
!example
** TBD **
!ACATS test
ACATS C-Test(s) are necessary for this package.
!appendix

From: Tucker Taft
Date: Tuesday, October 2, 2007  8:06 PM

As part of my homework for ASIS SI-24, the semantic
model, I am trying to work out a version using tagged
types rather than untagged discriminated types.
One thing we agreed would be necessary is some kind
of singleton container that can be used to store
class-wide values returned from various functions.
I have constructed such a container, which I chose
to call a "holder."  I have attached the proposed
spec and body, which I tried to make similar to
indefinite_vectors, etc.  There seems no particular
value for a holder of "definite" elements, so I
didn't bother to define both a "definite" version
and an "indefinite" version.  This one handles
either.

As you may remember, the key problem I saw with using
tagged types for the ASIS semantic model is that the operations
will often be designed to return a class-wide object, but then
the caller may want to be able to store this into
a preexisting local variable, or the field of some
record.  That doesn't work for a class-wide type.
However, a "holder" can be used for this purpose.

Comments welcome.  My intent is to define a Holder type
for each of the main kinds of views/entities.
The user could of course declare more if they
wanted finer control.

---

with Ada.Streams;
with Ada.Finalization;
generic
    type Element_Type (<>) is private;
package Ada_Containers_Holders is
    -- This package provides a "holder" of a definite type that contains
    -- a single value of an indefinite type.
    -- This allows one to effectively declare an uninitialized variable
    -- of an indefinite type.

    type Holder is tagged private;

    procedure Replace_Contents(
      Container : in out Holder; Contents : Element_Type);
      -- This assigns Contents into the Container, replacing
      -- any preexisting content of Container.

    generic
        with procedure Update(Contents : in out Element_Type);
    procedure Update_Contents(Container : in out Holder);
      -- This calls the given procedure with the contents, if
      -- any, of the Container.  It is a no-op if the
      -- container is empty.

    procedure Move(Target : in out Holder; Source : in out Holder);
      -- This moves the contents of Source into Target, leaving
      -- Source empty, and replacing any preexisting content of Target.
      -- This is a no-op if Target and Source are the same object.

    function Contents(Container : Holder) return Element_Type;
      -- This returns the element, if any, stored in the holder.
      -- If no element is stored in the holder, Constraint_Error is raised.

    function Is_Empty(Container : Holder) return Boolean;
      -- This returns True if the holder is empty, and
      -- False if it contains an element.

private
    type Element_Ptr is access Element_Type;
    type Holder is new Ada.Finalization.Controlled with record
	Contents : Element_Ptr := null;
    end record;

    procedure Adjust(Container : in out Holder);
      -- This makes a copy of Container.Contents.all 
      -- if Container.Contents non-null

    procedure Finalize(Container : in out Holder);
      -- This calls Unchecked_Deallocation on Container.Contents

    function "="(Left, Right : Holder) return Boolean;
      -- Make sure that equality works

    -- Provide stream attributes
    use Ada.Streams;

    procedure Write(Stream : access Root_Stream_Type'Class;
      Container : in Holder);

    for Holder'Write use Write;

    procedure Read(Stream : access Root_Stream_Type'Class;
      Container : out Holder);

    for Holder'Read use Read;

end Ada_Containers_Holders;

---

with Ada.Unchecked_Deallocation;
package body Ada_Containers_Holders is
    -- This package provides a definite type that contains
    -- a single value of an indefinite type.

    ------------- Local and private declarations --------------

    procedure Free is new 
      Ada.Unchecked_Deallocation(Element_Type, Element_Ptr);

    procedure Adjust(Container : in out Holder) is
      -- This makes a copy of Container.Contents.all if 
      -- Container.Contents non-null
    begin
	if Container.Contents /= null then
	    Container.Contents := new Element_Type'(Container.Contents.all);
	end if;
    end Adjust;

    procedure Finalize(Container : in out Holder) is
      -- This calls Unchecked_Deallocation on Container.Contents
    begin
	Free(Container.Contents);
    end Finalize;

    function "="(Left, Right : Holder) return Boolean is
      -- Make sure that equality works
    begin
	return Left.Contents = Right.Contents or else
	  (Left.Contents /= null and then Right.Contents /= null
	   and then Left.Contents.all = Right.Contents.all);
    end "=";

    -- Provide stream attributes
    procedure Write(Stream : access Root_Stream_Type'Class;
      Container : in Holder) is
	-- Write a boolean indicating whether Holder contains
	-- an element, and if True, write out the element.
	Is_Present : constant Boolean := Container.Contents /= null;
    begin
	Boolean'Write(Stream, Is_Present);
	if Is_Present then
	    Element_Type'Output(Stream, Container.Contents.all);
	end if;
    end Write;

    procedure Read(Stream : access Root_Stream_Type'Class;
      Container : out Holder) is
	-- Read a boolean, and then if true, read the element
	Is_Present : constant Boolean := Boolean'Input(Stream);
    begin
	Free(Container.Contents);
	if Is_Present then
	    Container.Contents := new Element_Type'(Element_Type'Input(Stream));
	end if;
    end Read;

    ------------ Visible Subprograms ---------------

    procedure Replace_Contents(
      Container : in out Holder; Contents : Element_Type) is
      -- This assigns Contents into the Container, replacing
      -- any preexisting content of Container.
    begin
	Free(Container.Contents);
        Container.Contents := new Element_Type'(Contents);
    end Replace_Contents;

    procedure Update_Contents(Container : in out Holder) is
      -- This calls the given procedure with the contents, if
      -- any, of the Container.  It is a no-op if the
      -- container is empty.
    begin
	if Container.Contents /= null then
	    Update(Container.Contents.all);
	end if;
    end Update_Contents;

    procedure Move(Target : in out Holder; Source : in out Holder) is
      -- This moves the contents of Source into Target, leaving
      -- Source empty, and replacing any preexisting content of Target.
      -- This is a no-op if Target and Source are the same object.
    begin
	if Target.Contents /= Source.Contents then
	    Free(Target.Contents);
	    Target.Contents := Source.Contents;
	    Source.Contents := null;
	end if;
	    -- NOTE: If Target.Contents = Source.Contents then
	    --       either both are null, or Target and Source are
	    --       the same object (or so I claim).
    end Move;

    function Contents(Container : Holder) return Element_Type is
      -- This returns the element, if any, stored in the holder.
      -- If no element is stored in the holder, Constraint_Error is raised.
    begin
	return Container.Contents.all;
    end Contents;

    function Is_Empty(Container : Holder) return Boolean is
      -- This returns True if the holder is empty, and
      -- False if it contains an element.
    begin
	return Container.Contents = null;
    end Is_Empty;

end Ada_Containers_Holders;

****************************************************************

From: Pascal Leroy
Date: Wednesday, October 3, 2007  12:53 AM

> Comments welcome.  My intent is to define a Holder type for 
> each of the main kinds of views/entities.
> The user could of course declare more if they wanted finer control.

It would seem to make sense to have a procedure Clear to empty the holder.
To some extent, Clear and Is_Empty go together.

****************************************************************

From: Jean-Pierre Rosen
Date: Wednesday, October 3, 2007  2:33 AM

> As part of my homework for ASIS SI-24, the semantic
> model, I am trying to work out a version using tagged
> types rather than untagged discriminated types.
> One thing we agreed would be necessary is some kind
> of singleton container that can be used to store
> class-wide values returned from various functions.
> I have constructed such a container, which I chose
> to call a "holder."  I have attached the proposed
> spec and body, which I tried to make similar to
> indefinite_vectors, etc.  There seems no particular
> value for a holder of "definite" elements, so I
> didn't bother to define both a "definite" version
> and an "indefinite" version.  This one handles
> either.

Looks nice and useful. So useful actually that it has nothing special to 
do with ASIS. Even if we take the opportunity to put this in ASIS05, is 
there a problem in naming it ada.containers.holders ? This could 
encourage implementations (even if they do not provide ASIS) to provide 
this one as part of the standard library.

****************************************************************

From: Tucker Taft
Date: Wednesday, October 3, 2007  12:24 PM

It was my hope that it could be named
Ada.Containers.Holders in the expectation that
it would be the first of various additions to the
Containers hierarchy.

I think I'll add something like "Clear,"
but call it "Set_Empty" to make
its relationship to "Is_Empty" crystal "clear".  ;-)

I was also considering changing the name of the formal
type to "Contents_Type" from "Element_Type," to
be consistent with the use of the function name "Contents"
rather than "Element".

****************************************************************

From: Tucker Taft
Date: Wednesday, October 3, 2007  12:41 PM

Actually, I guess "Clear" is used consistently
to mean Set_Empty in the other Containers, so
I should probably use that here as well.

****************************************************************

From: Pascal Leroy
Date: Wednesday, October 3, 2007  2:05 AM

> Actually, I guess "Clear" is used consistently to mean 
> Set_Empty in the other Containers, so I should probably use 
> that here as well.

I think consistency with the other containers would be a good thing.  So I
prefer Clear to Set_Empty and Element_Type to Content_Type.  No point in
changing names unless the semantics are clearly distinct.

****************************************************************

From: Randy Brukardt
Date: Wednesday, October 24, 2007  7:33 PM

For the record, this goes for all of the names (and wording!) in this container:

Update_Element, not Update_Contents;
Query_Element needs to exist;
Element, not Contents, to retrieve the element;
Replace_Element, not Replace_Contents;
and, finally, the operations need to be declared in the same order as in the 
other containers (otherwise, John will beat me up during editorial review).

I'll make all of these changes in the AI, so we don't (I hope) have to argue
about that for hours.

****************************************************************

From: Randy Brukardt
Date: Wednesday, October 24, 2007  8:06 PM

Having written that, I found more:
   Function wording starts with "Returns", not "This returns";
   Lots of other wording issues (copy the Vector container wording
     as closely as possible);
   The 2nd parameter name for Replace_Element should be New_Item;
   Query_Element and Update_Element should take an access-to-subprogram,
     not be generic.

Possibly controversial:
   Query_Element and Update_Element should raise Constraint_Error if the holder
   is empty to be consistent with Element (Tucker called it "Contents"). For existing
   containers, this is a Bounded_Error in all three cases, but none of the error
   possibilities is no-op (which is what Tucker used for Update_Element). It seems
   to me that defining this as a no-op is dangerous, because the update will not happen
   without any indication as to why; the possibility of using the existing (random)
   element only makes sense for definite containers.

   We also need the tampering and propagation rules.

   We should have a To_Holder function:
       function To_Holder (New_Item : Element_Type) return Holder;
          -- Returns a non-empty holder containing an element initialized to New_Item.
   This is similar to the To_Vector function of a vector. (But this one is not as common as
   the other operations.)

Yes, I looked at every routine in Vectors to see if it was relevant. That's probably
the only way to do this.

Amazing how many details there are even in such a simple container...

****************************************************************



Questions? Ask the ACAA Technical Agent