Version 1.2 of ais/ai-10260.txt

Unformatted version of ais/ai-10260.txt version 1.2
Other versions for file ais/ai-10260.txt

!standard 3.09 (06)          04-10-27 AI95-00260-02/02
!standard 3.09 (07)
!standard 3.09 (12)
!standard 3.09 (18)
!standard 3.09 (25)
!standard 3.09.02 (02)
!standard 3.09.03 (03)
!standard 3.09.03 (11)
!standard 12.06 (03)
!standard 12.06 (04)
!standard 12.06 (08)
!standard 12.06 (10)
!standard 12.06 (18)
!standard 13.13.2 (31)
!class amendment 04-09-08
!status work item 04-09-08
!status received 04-09-08
!priority Medium
!difficulty Medium
!subject How to control the tag representation in a stream
!summary
A new kind of generic formal subprogram, the abstract formal subprogram, is defined. It allows the importation of dispatching operations into a generic unit.
A pair of generic units are defined to allow dispatching to a routine with a controlling result. This unit makes it possible to write functions with similar semantics to T'Class'Input, including a complete replacement for the function.
A operation is defined in Ada.Tags to get the tag of a parent type.
!problem
S'Class'Output writes the tag of an item with String'Output. This routine cannot be replaced, so it is not possible to write the tag in an arbitrary user-defined format (to match an external standard, for example).
Consider, for example, XML, which is a text format. With the current language definition, it is not possible to redefine the 'Read and 'Write attribute to have the object streamed as an XML object. This is surprising, as external tags are defined as strings. It is possible to change the external tag string value by using Ada.Tags facilities. But the tag will always be streamed as an unbounded string object. This means that the tag string value will be written preceded by the string bounds -- which are binary data. There is no way to prevent this binary data from being sent. In other words, there is no way to stream an object with a plain text representation.
!proposal
(See wording.)
!wording
Add after 3.9(6):
No_Tag : constant Tag;
Add after 3.9(7):
function Parent_Tag (T : Tag) return Tag;
Add after 3.9(12):
The function Parent_Tag returns the tag of the parent type of the type whose tag is T. If the type does not have a parent type (that is, it was not declared by a derived_type_declaration), then No_Tag is returned.
AARM Note: The parent type is always the parent of the full type; a private extension appears to define a parent type, but it does not (only the various forms of derivation do that). As this is a run-time operation, ignoring privateness is OK.
Add after 3.9(18):
Static Semantics
The following language-defined generic functions exist:
generic type T (<>) is abstract tagged private; type Parameters (<>) is limited private; with function Constructor (Params : access Parameters) return T is abstract; function Ada.Tags.Generic_Dispatching_Constructor (The_Tag : Tag; Params : access Parameters) return T'Class; pragma Pure (Generic_Dispatching_Constructor); pragma Convention (Intrinsic, Generic_Dispatching_Constructor);
generic type T (<>) is abstract tagged limited private; type Parameters (<>) is limited private; with function Constructor (Params : access Parameters) return T is abstract; function Ada.Tags.Generic_Limited_Dispatching_Constructor (The_Tag : Tag; Params : access Parameters) return T'Class; pragma Pure (Generic_Limited_Dispatching_Constructor); pragma Convention (Intrinsic, Generic_Limited_Dispatching_Constructor);
Generic_Dispatching_Constructor and Generic_Limited_Dispatching_Constructor provide a mechanism to create an object of an appropriate type from just a tag value. This capability is sometimes known as a factory. The function Constructor does the actual creation of the object, using the information provided in a value of type Parameters to control the initial state of the object.
AARM Note: This specification is designed to make it easy to create dispatching constructors for streams; in particular, this can be used to construct overriddings for T'Class'Input.
Add after 3.9(25):
Tag_Error is raised by a call of Expanded_Name, External_Tag, or Parent_Tag if any tag passed is No_Tag.
[Editor's Note: If AI-344 is included in the Amendment, this rule should include Descendant_Tag and Is_Descendant_At_Same_Level.]
An instance of Generic_Dispatching_Constructor or Generic_Limited_Dispatching_Constructor raises Tag_Error if The_Tag does not represent a concrete descendant of T. Otherwise, it dispatches to the version of Constructor for the type identified by the tag The_Tag, passing Params, and returns the result.
AARM Note: The tag check checks both that The_Tag is in T'Class, and that it is not abstract. [Note: Those checks come from AI-279.]
Erroneous Execution
If the internal tag provided to an instance of Generic_Dispatching_Constructor or Generic_Limited_Dispatching_Constructor identifies a specific type whose tag has not been elaborated, or does not exist in the partition at the time of the call, execution is erroneous.
AARM Note: For a library-level type, this shouldn't be possible presuming that the tag value came from the current execution of the partition. T'Tag freezes the type (and thus elaborates the tag), and Internal_Tag and Descendant_Tag cannot return the tag of a library-level type that has not been elaborated. Finally, library-level types never cease to exist. Thus, if the tag comes from a library-level type, there cannot be erroneous execution (the use of Descendant_Tag rather than Internal_Tag can help insure this). [Note: This rule also comes from AI-279.]
Change the first sentence of 3.9.2(2):
A call on a dispatching operation is a call whose name or prefix denotes the declaration of a primitive operation of a tagged type {or an abstract formal subprogram}, that is, a dispatching operation.
Add after 3.9.3(3):
The subprogram declared by a formal_abstract_subprogram_declaration is an abstract subprogram.
[Editor's note: The second sentence of 3.9.3(3) does not apply to abstract formal subprograms; they have their own rules for controlling operands given in 12.6. Thus I did not add this rule to paragraph 3.9.3(3), but rather created a new paragraph. Luckily the original wording does not say that it applies to all abstract subprograms, so no change is needed to it.]
Replace the first sentence of 3.9.3(11) by:
A generic actual subprogram shall not be abstract unless the generic formal subprogram is a formal_abstract_subprogram_declaration.
Replace 12.6(2) by:
formal_subprogram_declaration ::= formal_abstract_subprogram_declaration | formal_concrete_subprogram_declaration formal_concrete_subprogram_declaration ::= with subprogram_specification [is subprogram_default]; formal_abstract_subprogram_declaration ::= with subprogram_specification is abstract;
Add after 12.6(8):
If a formal parameter of an formal_abstract_subprogram_declaration has a specific tagged type T or is an anonymous access designating a specific tagged type T, T is called a dispatching type of the formal_abstract_subprogram_declaration. Similarly, if the result of an formal_abstract_subprogram_declaration for a function has a specific tagged type T or is an anonymous access designating a specific tagged type T, T is called a dispatching type of the formal_abstract_subprogram_declaration. A formal_abstract_subprogram_declaration shall have exactly one dispatching type.
AARM Note: The specific tagged type could be any of a formal tagged private type, a formal derived type, or a normal tagged type. While the last case doesn't seem to be very useful, there isn't any good reason for disallowing it. This rule insures that the operation is a dispatching operation of some type, and that we unambiguously know what that type is. End AARM Note.
The actual subprogram for a formal_abstract_subprogram_declaration shall be a primitive operation of the dispatching type of the formal_abstract_subprogram_declaration.
AARM Note: This means that it is a dispatching operation of the dispatching type. Also note that this prevents the dispatching type from being class-wide, as only specific types have primitive operations. This could happen in a case like:
generic type T(<>) is tagged private; with procedure Foo (Obj : in T) is abstract; package P ...
package New_P is new P (Something'Class, Some_Proc);
The instantiation here is always illegal, because Some_Proc could never be a primitive operation of Something'Class (there are no such operations). That's good, because we want calls to Foo always to be dispatching calls. End AARM Note.
Add after 12.6(10):
The subprogram declared by a formal_abstract_subprogram_declaration with a dispatching type T is a dispatching operation of type T.
AARM Note: This is necessary to trigger all of the dispatching operation rules. It otherwise would not be considered a dispatching operation, as formal subprograms are never primitive operations.
Replace 12.6(18) by:
18 The actual subprogram cannot be abstract unless the formal subprogram is a formal_abstract_subprogram_declaration (see 3.9.3).
Add a new note after 12.6(18):
19 The subprogram declared by a formal_abstract_subprogram_declaration is an abstract subprogram. All calls on a subprogram declared by a formal_abstract_subprogram_declaration must be dispatching calls. See 3.9.3. [These things are defined in 3.9.3, but they are important to mention here.]
Replace 13.13.2(31) by:
First writes the external tag of Item to Stream (by calling String'Output(Stream, Tags.External_Tag(Item'Tag)) -- see 3.9) and then dispatches to the subprogram denoted by the Output attribute of the specific type identified by the tag.
[Editor's note: This corrects typos in the original paragraph.]
!discussion
A function like the instance of Generic_Dispatching_Constructor or T'Class'Input is often called a "factory" in OOP literature. Factories can be useful in scenarios other than streaming. See the examples for two possible uses.
The only part of T'Class'Input that cannot be written in Ada is the dispatching call to T'Input. This is the part that we need to model with a built-in operation. It's not necessary to create an object of the type; the called function will do that. That avoids problems with discriminants which aren't known.
Adding a new kind of generic formal for this problem is somewhat heavy. However, such a formal would be useful in other cases than this generic; Ada 95 has no way to define a dispatching generic formal. For instance, consider a generic that implemented a persistence add-in. If it had flattening/reconstruction formals that were dispatching, then a single instantiation could add persistence to an entire type hierarchy. (And this could be done without constraining the names of the subprograms or modifying the base class, as the use of interfaces would require.) The implementation of abstract formal subprograms is easy (it presumably would pass the slot number of the call for a sharing implementation).
We need separate limited and nonlimited versions of this generic, because nonlimited tagged types do not match limited tagged formal types. The limited version requires the definition of functions returning limited types as expressed by AI-318-2/06.
---
The Parent_Tag operation makes available to the user information that the compiler must have in order to do type conversion and membership checks.
---
An alternative considered was to use an interface to define the profile of a constructor. This could look like:
with Ada.Tags; generic type Parameters (<>) is limited private; package Ada.Generic_Dispatching_Construction is type Constructed is limited interface; function Constructor (Params : access Parameters) return Constructed is abstract; -- primitive op
function Dispatching_Constructor (Tag : Ada.Tags.Tag; Params : access Parameters) return Constructed'Class; -- not a primitive op end Ada.Generic_Dispatching_Construction;
with Ada.Streams; with Ada.Generic_Dispatching_Constructor; package Ada.Streaming_Construction is new Ada.Generic_Dispatching_Construction ( Parameters => Ada.Streams.Root_Stream_Type'Class);
Dispatching_Constructor raises Tag_Error if Tag does not represent a concrete descendant of Constructed. Otherwise, Dispatching_Constructor dispatches to the version of Constructor for the type identified by the tag Tag, passing Params, and returns the result.
The advantage of this is that no new kind of generic formal subprogram is needed.
However, this solution has several minor problems and one significant one. With apologies to David Letterman, here are the top 4 reasons that this isn't the best solution:
4) This cannot be used to describe the semantics of T'Class'Input. That
means the rules of AI-279 have to be duplicated in both the constructor and the description of T'Class'Input. (They're fairly short, though, and we did that anyway).
3) To use this to redefine T'Class'Input, the base class has to be
modified to add this interface.
2) The constructor function has to be named Constructor. A more appropriate
name cannot be used.
And the number one reason that this is not the best solution: 1) This only allows one instance of a dispatching constructor per type
hierarchy. (That's because of (2): there is only one name for the Constructor function.) It would be possible to use T'Class'Input in addition to this constructor. But the point of adding this generic is to allow users to create this sort of functionality when it is needed. Allowing only a single instance of it is hardly any better than the current situation where the only way to get this functionality is to hijack T'Class'Input. The solution should not limit the number of factories that can be created for a given type hierarchy.
---
This is presented as an alternative to the Tag_Read and Tag_Write attributes. It would be possible to support both, but there doesn't seem to be any strong need to do so. As shown below, with this proposal overridding T'Class'Input and T'Class'Output is no more difficult than implementing Tag_Read and Tag_Write.
In addition, AI-344 forces the specification of Tag_Read to be:
function S'Class'Tag_Read (
Stream : access Streams.Root_Stream_Type'Class; Ancestor : Ada.Tags.Tag) return Ada.Tags.Tag;
as the ancestor parameter is necessary to implement the default functionality. However, it would rarely (if ever) be useful in an overridden implementation. And this makes Tag_Read and Tag_Write less symmetrical. Also note that the complete replacement approach means that we don't have to specify "shoulds" about the behavior of the attributes; whatever the user writes is what it will do, and if that doesn't make sense it is the user's problem.
!example
Here is an example that illustrates the problem. Suppose that we want to stream an object with an XML encoding:
with Ada.Streams; package Class is
type Object is tagged record V : Integer := 2; end record;
procedure Write (S : access Ada.Streams.Root_Stream_Type'Class; O : Object); for Object'Write use Write;
for Object'External_Tag use "<object>";
end Class;
package body Class is
procedure Write (S : access Ada.Streams.Root_Stream_Type'Class; O : Object) is begin String'Write (S, "<v>"); String'Write (S, Integer'Image (O.V)); String'Write (S, "</v>"); String'Write (S, "</object>"); end Write;
end Class;
with Ada.Text_IO.Text_Streams; with Class;
procedure Main is O : Class.Object; begin Class.Object'Class'Output (Ada.Text_IO.Text_Streams.Stream (Ada.Text_IO.Current_Output), O); end Main;
This program output will be something like:
^A^@^@^@^H^@^@^@<object><v> 2</v></object>
The first 8 bytes (characters) are the binary representation for the tag bound. In Ada 95, there is no way to prevent these bytes from being written, so there is no way to stream an object with an XML representation.
Using Generic_Dispatching_Constructor, it is possible to override T'Class'Input and T'Class'Output to read/write the proper format.
with Ada.Streams, Ada.Tags; package Class is
type Object is tagged record V : Integer := 2; end record;
for Object'External_Tag use "object";
procedure Write (S : access Ada.Streams.Root_Stream_Type'Class; O : Object); for Object'Write use Write;
procedure Class_Output (S : access Ada.Streams.Root_Stream_Type'Class; O : Object'Class); for Object'Class'Output use Class_Output;
function Class_Input (S : access Ada.Streams.Root_Stream_Type'Class) return Object'Class; for Object'Class'Input use Class_Input;
end Class;
package body Class is
procedure Write (S : access Ada.Streams.Root_Stream_Type'Class; O : Object) is begin String'Write (S, "<v>"); String'Write (S, Integer'Image (O.V)); String'Write (S, "</v>"); String'Write (S, "</object>"); end Write;
procedure Class_Output (S : access Ada.Streams.Root_Stream_Type'Class; O : Object'Class) is -- Write a tag of the form "<External_Tag>", then the object. begin Character'Write (S, '<'); String'Write (S, Ada.Tags.External_Tag (Tag)); Character'Write (S, '>'); Object'Output (S, O); -- Dispatching call; calls Object'Write. end Class_Output;
function Dispatching_Input is new Generic_Dispatching_Constructor (T => Object, Parameters => Ada.Streams.Root_Stream_Type'Class, Constructor => Object'Input);
function Class_Input (S : access Ada.Streams.Root_Stream_Type'Class) return Object'Class is -- Read a tag of the form "<External_Tag>", then dispatch to reading -- the object. Input : String (1..20); Input_Len : Natural := 0; begin Character'Read (S, Input(1)); if Input(1) /= '<' then raise Ada.Tags.Tag_Error; end if; Input_Len := 0; for I in Input'range loop Input_Len := I; Character'Read (S, Input(I)); exit when Input(I) = '>'; end loop; if Input(Input_Len) /= '>' or else -- Never found closing character Input_Len <= 1 then -- Empty tag raise Ada.Tags.Tag_Error; end if; return Dispatching_Input (Ada.Tags.Internal_Tag (Input(1..Input_Len), S); -- Dispatches to appropriate Object'Input for the tag read. end Class_Input;
end Class;
We now have the following output:
<object><v> 2</v></object>
which is what we need to write XML.
Note that overridding the stream attributes is not necessarily appropriate (for instance, if a distributed program needed to marshall these objects). This proposal provides a way to provide this functionality without using the stream attributes directly (just use the subprograms directly, and avoid defining stream attributes).
---
This proposal can also be used in other cases where object construction of a type determined at run-time is needed. For instance, a GUI builder might want to create an object of a particular type based on the selection of the user from a menu. Indeed, the Claw GUI builder does exactly this in Ada 95. Because there is no way to write a constructor, Claw uses a giant case statement, with the resulting maintenance problems.
A Generic_Dispatching_Constructor instance would do this job better. We assume that each control or window registers itself with the menu manager as it elaborates. The registeration would save the tag of the type along with its menu selection name.
In order to do that, we'd create a Construct function in the Root_Window_Type package:
type Construct_Params is null record; -- Probably would include data about -- the parent window here. function Construct (Params : access Construct_Params) return Root_Window_Type;
(and of course in all of the descendants), then
function Factory is new Generic_Dispatching_Constructor (Root_Window_Type,
Construct_Params, Construct);
function Create_Object (Item_Tag : in Ada.Tags.Tag) return Access_Any_Window is Params : aliased Construct_Params; begin return new Root_Window_Type'Class'(Factory (Params)); end Create_Object;
Then the menu action routine (which handles menu selections) could simply call Create_Object with the tag identified by the menu item selected by the user. This structure eliminates the need for any case statements anywhere in the program, so adding or removing a control or window doesn't require modifying a giant case statement (which compiles slowly, as it imports the entire universe).
Note that a GUI builder needs to stream these objects (to save them into a GUI project file) as well as construct them from a menu, so overridding T'Class'Input is not an option.
--!corrigendum 13.13.2(28)
!ACATS test
A C-Test should be constructed to test this feature.
!appendix

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


Questions? Ask the ACAA Technical Agent