Version 1.1 of ais/ai-10260.txt
!standard 3.09 (18) 04-09-08 AI95-00260-02/01
!standard 3.09 (25)
!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 generic unit is 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.
!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(18):
Static Semantics
The following language-defined generic function exists:
generic
type T (<>) is abstract tagged private;
type Parameters (<>) is limited private;
with function Constructor (Params : access Parameters) return T;
function Generic_Dispatching_Constructor (Tag : Ada.Tags.Tag;
Params : access Parameters)
return T'Class;
Raises Tag_Error if Tag does not represent a concrete descendant of T.
AARM Note: This checks both that Tag is in T'Class, and that it is not
abstract. [Note: These rules come from AI-279.]
Otherwise, dispatches to the version of Constructor for the type identified
by the tag Tag, passing Params, and returns the result.
Legality Rules
The actual parameter for function Constructor in an instantiation of
Generic_Dispatching_Constructor shall be a primitive operation of type T.
Notwithstanding what it says elsewhere in this International Standard,
the actual subprogram for function Constructor in an instantiation of
Generic_Dispatching_Constructor may be abstract.
Add after 3.9(25):
Erroneous Execution
If the internal tag provided to an instance of Generic_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 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
escendant_Tag rather than Internal_Tag can help insure this). [Note: This rule
also comes from AI-279.]
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 scenar ios 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. This avoids problems with discriminants which aren't
known.
The two legality rules are somewhat uncomfortable, because they differ from
other generic instantiations. This is not too bad, as this generic cannot be
written in anything close to Ada, and thus will require special handling as
does Unchecked_Conversion.
Both legality rules could be made more consistent by defining a new kind of
generic formal subprogram that required a primitive dispatching routine.
One possibility would be
with overridding of T <subprogram_spec> [is <default>];
with the legality rules moved to appropriate places in 3.9.3(11) and 12.6.
(That is, the actual could be abstract and would have to be primitive for
type T; T would have to be a specific tagged type (including formal
tagged types)). Such a formal would be useful in other cases than this generic;
there currently is no way to define a dispatching generic formal. For instance,
consider a generic that implemented a persistence add-in. If it could have
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
would be easy (it presumably would pass the slot number of the call).
But this seems too complex a change for this problem.
The first legality rule could be eliminated either by defining a run-time
check that raises an exception if the function is not dispatching, or by
simply saying that a non-dispatching actual is just called. The first option
does nothing except postpone a check to a later time, and the second option
seems to be covering a bug.
The second legality rule is needed to override 3.9.3(11) for this generic.
That's necessary so that interfaces and abstract types can be passed into
the generic. We could eliminate this rule (and the 'abstract' on type T),
but that would make the generic useable on concrete types only, while most
root classes are abstract (or possibly an interface).
If the final version of AI-318 allows it, there should be a limited version
of Generic_Dispatching_Constructor with T being limited. (This is not allowed
by AI-318-2/05.)
---
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; --
function Dispatching_Constructor
(Tag : Ada.Tags.Tag;
Params : access Parameters)
return Constructed'Class; --
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 special legality rules are 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).
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 its 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 the 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
--
begin
Character'Write (S, '<');
String'Write (S, Ada.Tags.External_Tag (Tag));
Character'Write (S, '>');
Object'Output (S, O); --
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
--
--
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 --
Input_Len <= 1 then --
raise Ada.Tags.Tag_Error;
end if;
return Dispatching_Input (Ada.Tags.Internal_Tag (Input(1..Input_Len), S);
--
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; --
--
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
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