Version 1.2 of ai05s/ai05-0074-5.txt
!standard 12.3 (12) 09-10-14 AI05-0074-5/01
!standard 12.3.1(0)
!class amendment 09-10-14
!status work item 09-10-14
!status received 09-06-13
!priority Medium
!difficulty Hard
!subject Private Instantiations - Take 2
!summary
(See proposal.)
!problem
Ada 95 provides formal package parameters. One way of using these parameters
is to define a "signature" for a class of abstractions, such as all set
abstractions, or all physical unit abstractions, and then build a new
generic abstraction on top of the original class of abstractions using this
signature as the template for a formal package parameter.
Unfortunately, it is difficult to use signatures because of the fact that an
instantiation freezes all of its actual parameters.
For example:
Given the signature for a set abstraction:
generic
type Element is private;
type Set is private;
with function Size(Of_Set : Set) return Natural is <>;
with function Union(Left, Right : Set) return Set is <>;
with function Intersection(Left, Right : Set) return Set is <>;
with function Empty return Set is <>;
with function Unit_Set(With_Item : Element) return Set is <>;
with function Nth_Element(Of_Set : Set) return Element is <>;
package Set_Signature is end;
...
we could define a generic that required some set abstraction, but it
didn't care which one so long as it implemented the above signature:
generic
with package Base_Set is new Set_Signature(<>);
package Layered_Abstraction is
type Cool_Type(Set : access Base_Set.Set) is limited_private;
procedure Massage(CT : in out Cool_Type; New_Set : Base_Set.Set);
...
end Layered_Abstraction;
Now if we want to define a set type and provide the pre-instantiated
signature for it, we run into trouble:
generic
type Elem is private;
with function Hash(El : Elem) return Integer;
package Hashed_Sets is
type Set is private;
function Union(Left, Right : Set) return Set;
...
package Signature is new Set_Signature(Elem, Set);
private
type Set is record ... end record;
end Hashed_Sets;
The problem is that we can't do the instantiation of Set_Signature where we
would want to do so, because the instantiation freezes the type "Set"
prematurely.
A similar problem occurs when a type wants to include a pointer to a
container based on the type being defined.
For example:
with Sequences;
package Expressions is
type Expr_Ref is private;
package Expr_Sequences is new Sequences(Expr_Ref); --
type Seq_Of_Expr is access Expr_Sequences.Sequence;
function Operands(Expr : Expr_Ref) return Seq_Of_Expr;
...
private
type Expression; --
type Expr_Ref is access Expression;
end Expressions;
Here we have a case where we want to instantiate a generic using a private
type, and use the results of the instantiation as the designated type of an
access type, which is in turn used as a parameter or result type of an
operation on the private type.
Unfortunately, we can't instantiate the "Sequences" generic with Expr_Ref,
since it is private.
!proposal
Introduce new syntax (a private_instantiation_declaration) that exports a
view of a generic instance declared in the private part of a package
specification so that a type with a partial view in the visible part of the
package can be used as an actual in the instantiation.
The real generic_instantiation that completes a
private_instantiation_declaration is explicitly placed after the
full_type_declaration that completes the type with the partial view, so there
are no freezing concerns with the generic instance, nor is there a need to
change rules or add restrictions to the generic contract model.
The view of the instance at the site of the private_instantiation_declaration
is equivalent to the view of the generic instantiation within the private part,
except that there are rules that restrict how this view may be used within the
immediately enclosing declarative region between the private instantiation
declaration and its completion in the private part.
A private type or private extension declared within the immediately enclosing
declarative region containing a private instantiation declaration cannot
directly contain a component of a composite type exported from the view of the
private instantiation declaration since such an exported type may have been
derived from a formal which may be a private type or private extension that has
not yet been completed.
The declaration of a component that is an array type exported from the view of
the instance is allowed within the immediately enclosing declaration region
however, but only if the components of the array are not composite types,
formal types, or formal derived types of the instance. These rules are needed
to prevent circular type definitions where the completion of a private type or
private extension contains components exported from the private instantiation
declaration that are based on the same type.
If the view of a type exported from the view of a private instantiation declaration
is a tagged view, then private extension derivations of that type are allowed
within the immediately enclosing declarative region.
The declaration of objects of types exported from the view of a private
instantiation declaration within the immediately enclosing declarative region
before the completing generic instantiation are not allowed, since objects may
be associated with private types or private extensions that have not yet been
completed. Similarly, objects exported from the view of the private
instantiation declaration may not be directly referenced within the immediately
enclosing declarative region before the declaration of the generic instantiation
that completes the private instantiation declaration.
In addition, calls may not be made to functions exported from the view of
private instantiation declaration within the immediately enclosing declarative
region before the declaration of the completing generic instantiation, since
such function calls may have side affects that involve changes to objects
exported from the view of the instance, and such object may be based on
formals that are private types or private extensions that have not yet been
completed.
!wording
Change 12.3 (12)
A generic_instantiation declares {(a view of)} an instance; it is equivalent to
the instance declaration (a package_declaration or subprogram_declaration)
immediately followed by the instance body, both at the place of the
instantiation.
Add new section:
12.3.1 Private Instantiations
Add 12.3.1(1 - 31):
1 The declaration (in the visible part of a package) of a private instantiation
serves to allow a generic package to be instantiated with an actual that has a
partial view of a type.
SYNTAX
2 private_instantiation_declaration ::=
package defining_program_unit_name is
private new generic_package_name [deferred_generic_actual_part];
3 deferred_generic_actual_part ::=
(deferred_generic_association {, deferred_generic_association})
4 deferred_generic_association ::=
[generic_formal_parameter_selector_name =>] explicit_generic_actual_parameter
5 A deferred_generic_association is named or positional according to whether
or not the generic_formal_parameter_selector_name is specified. Any positional
associations shall precede any named associations.
LEGALITY RULES
6 A private_instantiation_declaration declares a View of an instance; such a
declaration is allowed only as a declarative_item of the visible part of a
package, and it requires a completion, which shall be a generic_instantiation of
a package. The completion of a private_instantiation_declaration occurs as a
declarative_item of the private part of the package.
7 The generic_formal_parameter_selector_name of a deferred_generic_association
shall denote a generic_formal_parameter_declaration of the generic unit being
instantiated. If two or more formal subprograms have the same defining name,
then named associations are not allowed for the corresponding actuals.
8 A private_instantiation_declaration shall contain at most one
deferred_generic_association for each formal. Each formal without an association
shall have a default_expression or subprogram_default.
9 The actual parameters of a private_instantiation_declaration shall match the
corresponding actual parameters of the completion, whether the actual
parameters are given explicitly or by default.
10 The rules for matching of actual parameters between the completion and the
private instantiation are as follows:
11 * If an association is a named association in the private instantiation
then the association shall be a named association in the completion. If an
association is a positional association in the private instantiation then the
association shall be positional in the completion.
12 * For a formal object of mode IN the actuals match if they are static
expressions with the same value, of if they statically denote the same
constant, or if they are both the literal NULL.
8 * For a formal subtype, the actuals match if they denote statically
matching subtypes.
9 * For other kinds of formals, the actuals match if they statically
denote the same entity.
AARM NOTE: We considered using full conformance rules here instead of
formal-package-ish matching. However, we wanted to use rules consistent
with formal packages, and it seemed simpler to just define the particular
matching rules needed between instantiations. Also we wanted to
ensure that no evaluations would take place at the point of the
private instantiation.
10 A private_instantiation_declaration shall not have a generic_association with
an explicit_generic_actual_parameter that has an incomplete view at the place
of the private_instantiation_declaration.
11 A type shall be completely defined before it is frozen (see 3.11.1 and
13.14). Thus, neither the declaration of a variable of a type from a view of an
instance, nor the creation by an allocator of an object of a type from the view
of the instance, are allowed before the completion of a
private_instantiation_declaration. Similarly, before the completion, such a
type cannot be used in a generic_instantiation or in a representation item. In
addition, a callable entity from the view of the instance cannot be called
before the completion of a private_instantiation_declaration, nor may any
objects from the view of the instance be directly referenced.
AARM 11.a Reason: A callable entity from a view of an instance can have side
affects that can modify the state of variables exported from the view of an
instance. We disallow function calls into an instance from a private
instantiation declaration to avoid problems involving usage of types that
have not yet been completely defined.
12 The view of a composite type of a
partial view of an instance (see 12.3.1) that does not have an incomplete view
and is not an array type, is a partial view of the type
13 All visible composite types other than array types with components that are
not of a composite type from the view of an instance associated with a
private_instantiation_declaration may not directly appear as a component in the
declaration of another composite type within the immediately enclosing
declarative region before the declaration of the generic instantiation that
completes the private_instantiation_declaration.
AARM
13.a Ramification: It is assumed that all such composite types have been derived
from a formal type that has a partial view, or contains a component of a formal
type that has a partial view (An assume the worst rule). This rule is needed to
prevents the declaration of illegal recursive types,or declaring other composite
types with components that have are not yet completely defined. Similarly, a
derivative of an array type is allowed to be declared, but if the components
of the array are composite types then the array is considered to be an array of
types that have a partial view.
14 The partial view of a type of an explicit_generic_actual_parameter of a
deferred_generic_association must conform to the formal type declaration
of the generic package specification. Specifically, if the partial view of a
type is a limited view, then the formal_private_type_definition shall have the
reserved word LIMITED in the formal type declaration. If the partial view of a
type is an untagged view, then the formal_private_type_definition shall not have
the reserved word TAGGED in the formal type declaration.
AARM
15.a This rule is needed because the full view of an untagged limited partial
view may be of a type with an unlimited view. Similarly, a full view of an
untagged partial view may be of a type that has a tagged view. We do not want to
allow a generic_instantiation in the private part of a package that conflicts
with the declarations of the actuals used in a
private_instantiation_declaration.
STATIC SEMANTICS
16 A private_instantiation_declaration declares a private instantiation.
17 A private_instantiation_declaration defines a view of a generic instance. The
view is equivalent to the view of the instance declared by the
generic_instantiation that completes the private_instantiation_declaration.
NOTES
18 Type derivations of visible types from the view associated with a
private_instantiation_declaration are not allowed within the immediately
enclosing declarative region before the completion of the
private_instantiation_declaration unless the declaration is a tagged private
extension, since such a type may have been derived (directly or indirectly) from
an actual that has a partial view.
!discussion
This proposal solves several problems that come up frequently in practice with
private types. Certainly the most common is a desire to include an instantiation
of a generic (possibly a signature generic as mentioned in the "problem" section
above, or perhaps a container generic), with a private type as one of the actual
parameters to the instantiation.
A second problem is a desire to be able to have a recursive data structure where
a type declaration can contain a component that designates a type from an
instance that was instantiated with an actual of the composite type.
An instantiation requires the type of an actual to be defined first, and since
the instantiation comes after the type declaration, the type declaration cannot
name any type declarations of the instantiation.
A third problem extends from the second problem as it is desirable that it be
possible to declare such a recursive data structure that has a primitive that
has a formal parameter of the type of a component that designates an object of
an instantiated type or returns a result value of a component that designates an
object of an instantiated type.
A fourth problem is another variant of the third, where it is desirable that it
be possible to declare such a recursive data structure that has a primitive that
has a formal parameter of the type of a component that is an instantiated type,
or returns a result value of a component that is an instantiated type.
In addition, we would like it to be possible to declare such a recursive type
as a remote access to class wide type allowing usage in distributed
applications.
A fifth problem is a desire to export a type derived from a tagged type that is
exported from a generic instance that has a private type as one of the actual
parameters.
The basic idea of this proposal is to add syntax that provides more flexibility
and capabilities for exporting functionality in the visible part
of a package specification and creating recursive data structures.
This proposal is limited to packages because you can use renaming-as-body to get
these effects for subprogram instantiations. Moreover, you cannot export a type
from a generic subprogram, so there is no possibility of recursive use.
[Note that we could lift this limitation if it if felt to be more regular.]
Ada 2005 provides solutions to some of these problems, but they are not ideal.
For the first example, making the instantiation a child unit solves the problem.
However, this is annoying, because accessing the instance requires an extra with
and instantiation (since children of generics must be generic):
generic
type Elem is private;
with function Hash (El : Elem) return Integer;
package Hashed_Sets is
type Set is private;
function Union(Left, Right : Set) return Set;
...
private
type Set is record ... end record;
end Hashed_Sets;
generic
package.Hashed_Sets.Signature is
package The_Signature is new Set_Signature(Elem, Set);
end Hashed_Sets.Signature;
A user of Hashed_Sets must with and instantiate Hashed_Sets.Signature in order
to access the instance.
A simpler alternative involves the use of nested packages. This is better, but
introducing a nested package can still be considered to be awkward.
generic
type Elem is private;
with function Hash (El : Elem) return Integer;
package Hashed_Sets is
package Sets is
type Set is private;
function Union(Left, Right : Set) return Set;
private
type Set is record ... end record;
end Sets;
use Sets;
package The_Signature is new Set_Signature(Elem, Set);
end Hashed_Sets;
The second problem can also be solved in some cases with child units,
using the limited with:
with Sequences;
limited with Expressions.Sequences;
package Expressions is
type Expr_Ref is private;
type Seq_Of_Expr is access Expressions.Sequences.Sequence;
function Operands(Expr : Expr_Ref) return Seq_Of_Expr;
...
private
type Expression; --
type Expr_Ref is access Expression;
end Expressions;
package Expressions.Sequences is
package Expr_Sequences is new Sequences(Expr_Ref);
type Sequence is new Expr_Sequences.Sequence;
end Expressions.Sequences;
Here, besides the extra with clause, we need to declare an extra type
simply so that the type is visible to the limited with clause
(which operates purely syntactically). This means that extra type conversions
are necessary. Another limitation is that this technique requires a type
derivation (Sequence), and would not work for types that cannot be extended,
such as synchronized tagged types.
Once again, the use of nested packages provides a simpler work around,
but this still requires the extra type and having to declare a nested
package can still be viewed as being awkward. Such a technique is something that
would likely require the awareness of an expert Ada programmer, as it is
questionable whether a less experienced programmer would come up with this
technique on their own.
with Sequences;
package Expression_Sequences is
type Expr_Seq; --
type Seq_Of_Expr is access Expr_Seq;
package Expressions is
type Expr_Ref is private;
function Operands(Expr : Expr_Ref) return Seq_Of_Expr;
...
private
type Expression; --
type Expr_Ref is access Expression;
end Expressions;
use Expressions;
package Expr_Sequences is new Sequences(Expr_Ref);
type Expr_Seq is new Expr_Sequences.Sequence; --
end Expression_Sequences;
This proposal attempts to provide a more intuitive approach for solving these
and other problems and eliminates limitations that currently exist in the
language.
First of all, an important capability is to be able to instantiate a generic
with a private type or private extension as an actual. The concept is to place
the generic instantiation in the private part after the completion of the
private type or private view, but provide a view of the instance in the visible
part of the package that is exported to outside program units.
This new syntactic construct is called a private_instantiation_declaration.
This is a declaration that is placed in the visible part of a package. It is
completed by a generic_instantiation in the private part of the package.
The view of the instance provided by the private_instantiation_declaration is
equivalent to the view of the instance provided by the generic instantiation,
though there are restrictions on how this view may be used within the package
specification before the completion of the declaration in the private part.
Other program units that reference the package do not have these restrictions
however.
A private_instantiation_declaration can be distinguished from a
generic_instantiation by the use of the word PRIVATE in the syntax.
e.g.,
with Ada.Containers.Doubly_Linked_Lists;
package Pkg is
type T is private;
package I is private new Ada.Containers.Doubly_Linked_Lists (T);
private
type T is null record;
package I is new Ada.Containers.Doubly_Linked_Lists (T);
end Pkg;
The intent is that the exported view of the instance of I is effectively the
same as the view if the generic had been instantiated from outside the
enclosing package;
i.e.
with Ada.Containers.Doubly_Linked_Lists;
package Pkg is
package Nested is
type T is private;
private
type T is null record;
end Nested;
use Nested; --
package I is new Ada.Containers.Doubly_Linked_Lists (T);
end Pkg;
Now suppose we would like type T to be a recursive structure having a
component that designates a type provided by the partial view of the instance.
In Ada 2005, it is currently possible to achieve this using an approach
involving a nested package and an Incomplete Type declaration.
e.g.,
with Ada.Containers.Doubly_Linked_Lists;
package Pkg is
type List_Type;
package Nested is
type T is private;
private
type T is
record
List : access List_Type;
end record;
end Nested;
use Nested; --
package I is new Ada.Containers.Doubly_Linked_Lists (T);
type List_Type is new I.List with null record;
--
end Pkg;
Note that the programmer really just wants to instantiate a list of T in the
visible part of the package. Here the programmer has had to jump through three
mental hurdles to achieve this. The programmer has to think of declaring a
nested package, declaring an incomplete type outside the nested package, and
completing the an incomplete type with an derived type from an instance. It is
quite likely that a reasonably experienced programmer would not come up with
this solution on their own, and this would have to be filed away in a book of
"tricks" to get around programming problems that one might run into.
This proposal provides an more intuitive syntax that more closely maps to
what the programmer wanted to express. Compare the complexity of the visible
part of the example above, with the two line visible part using the new proposed
syntax below. One cannot help but have to mentally process the private part
in the example above as well, whereas in the example below, the mind is much
more at ease without having to peek at the private part. The improved
readability of the new syntax should continue to generate savings over
the maintainance life cycle of the software.
e.g.
with Ada.Containers.Doubly_Linked_Lists;
package Pkg is
type T is private;
package I is private new Ada.Containers.Doubly_Linked_Lists (T);
private
type T is
record;
List : access I.List;
end record;
package I is new Ada.Containers.Doubly_Linked_Lists (T);
end Pkg;
Note also, that if we replace the use of Ada.Containers.Doubly_Linked_List with
a different container such as a protected queue that inherits from a synchronous
interface, then the Ada 2005 example above wont even compile whereas the
new proposed syntax will compile. This is because it is currently not possible
to derive a new type from a synchronized tagged type. The Ada 2005 version
relies on a type derivation of the private type whereas the new proposed
syntax does not involve any type derivations. One might argue that perhaps
Ada 2005 should be modified to allow derivations of synchronized tagged types.
That may be true and perhaps that capability will be added to the language
at some point in the future, but still it is less than ideal that the programmer
is being forced to derive a new type here, when it really has nothing to do with
the abstraction.
Taking this example one step further. Suppose we now want to introduce a
primitive subprogram that exports the value of a recursive component. In Ada
2005, the workaround example now becomes;
with Ada.Containers.Doubly_Linked_Lists;
package Pkg is
type List_Type;
type List_Ref is access List_Type;
package Nested is
type T is private;
function Get_List (Item : T) return List_Ref;
--
--
private
type T is
record
List : List_Ref;
end record;
end Nested;
use Nested; --
package I is new Ada.Containers.Doubly_Linked_Lists (T);
type List_Type is new I.List with null record;
end Pkg;
Here we needed to introduce a named access type because otherwise the
Get_List subprogram would generate a compile error because the Get_List
function cannot be dispatching on more than one type.
With the new proposed syntax, there is little change other than the additional
primitive, since the I.List has a partial view, it can be returned with an
anonymous access type. With the proposed syntax, you have more of a choice
whether to use named access types or not. Someone reading the visible parts
of both packages should get an understanding of the package below more readily.
with Ada.Containers.Doubly_Linked_Lists;
package Pkg is
type T is private;
package I is private new Ada.Containers.Doubly_Linked_Lists (T);
function Get_List (Item : T) return access I.List;
private
type T is
record;
List : access I.List;
end record;
package I is new Ada.Containers.Doubly_Linked_Lists (T);
end Pkg;
Now consider that we would like to make T a tagged type and we would like to
export a primitive of the tagged private type that returns the value of an
object of an instantiated type that is designated by a component.
In Ada 2005, we would modify the workaround example as follows;
e.g.,
with Ada.Containers.Doubly_Linked_Lists;
package Pkg is
type List_Type;
type List_Ref is access List_Type;
package Nested is
type T is tagged private;
private
type T is tagged
record
List : List_Ref;
end record;
end Nested;
use Nested; --
package I is new Ada.Containers.Doubly_Linked_Lists (T);
type List_Type is new I.List with null record;
function Get_List (Item : T) return List_Type;
end Pkg;
The Get_List function has to be declared after the instantiation outside
the nested package where T is declared, since List_Type is an incomplete type
from within the context of the Nested package and it is not possible to declare
a function that returns a value of an incomplete type. The problem is that
Get_List is not a primitive function of T. Any derivations of T do not include
the Get_List primitive.
With the new proposal, this is not a problem because the type I.List has a
partial view and can be returned as a result type. This means that Get_List can
be declared as a primitive of T, and thus is inherited by any derived types of
T. The change in the result type from an access type to a normal type had
a significant impact on the design of the program written using the existing
syntax. Using the new proposed syntax, the only change needed was to change
the result type.
with Ada.Containers.Doubly_Linked_Lists;
package Pkg is
type T is tagged private;
package I is private new Ada.Containers.Doubly_Linked_Lists (T);
function Get_List (Item : T) return I.List;
private
type T is tagged
record;
List : access I.List;
end record;
package I is new Ada.Containers.Doubly_Linked_Lists (T);
end Pkg;
Now suppose we would like to go back to returning an access to an instantiation,
but this time, we want the instantiation to be a synchronous tagged type.
In this example, we have instantiate a generic with an object of a limited
interface representing a work item, and the generic gives us a worker task
to operate on the item.
package Work_Item is
type Job is limited interface;
procedure Analyze (Item : in out Job) is null;
end Work_Item;
generic
type Work is limited new Job with private;
package Work_Item.Worker is
task type Periodic_Worker is new Job with
...
end Periodic_Worker;
end Work_Item.Worker;
We would like to have Get_Worker as a primitive function of our new type,
but we cant, since that requires a derivation of a synchronous tagged type.
We end up having to make Get_Worker a non-primitive function using current
Ada 2005 syntax.
with Work_Item.Worker;
package Pkg is
type Worker_Task;
type Worker_Access is access Worker_Task;
package Nested is
type T is limited new Work_Item.Job with private;
overriding procedure Analyze (Item : in out T);
function Get_Worker (Item : T) return Worker_Access;
private
type T is limited new Work_Item.Job with
record
...
end record;
end Nested;
use Nested;
package T_Worker is new Work_Item.Worker (Work => T);
type Worker_Task is new T_Worker.Periodic_Worker with null record;
--
--
function Get_Worker (Item : T) return access T_Worker.Periodic_Worker;
end Pkg;
Using the new proposed syntax, we can do what we want to do. If we decide
we want the Get_Worker primitive to return a task instead of an access to
task, simply delete the word access in the following example.
with Work_Item.Worker;
package Pkg is
type T is limited new Work_Item.Job with private;
overriding procedure Analyze (Item : in out T);
package T_Worker is private new Work_Item.Worker (Work => T);
function Get_Worker (Item : T) return access T_Worker.Periodic_Worker;
private
type T is limited new Work_Item.Job with
record
...
end record;
package T_Worker is new Work_Item.Worker (Work => T);
end Pkg;
Now suppose that we want to create a set of general purpose containers that
utilize an iterator interface.
generic
type Cursor is private;
No_Element : in Cursor;
package Ada.Iterator_Interfaces is
type Basic_Iterator is limited interface;
function First
(Object : Basic_Iterator) return Cursor is abstract;
function Next (Object : Basic_Iterator; Position : Cursor)
return Cursor is abstract;
end Ada.Iterator_Interfaces;
For this example, we will modify the existing
standard Doubly_Linked_Lists container to show how this might be done. The
biggest difficulty is that a cursor needs a reference to a node and to the full
container (the List in this example). The node probably could be defined in the
private part of package Cursors, but we can't define a reference to List in the
private part -- it hasn't been declared yet! And it can't be declared yet,
because it depends on the interface that we haven't yet created.
One way to workaround this problem is to use a Taft-amendment type in the
private part of Cursors. But we can't complete that with the type List
itself; we'd have to use a derived type. And that is undesirable as it would
force many junk type conversions. Even using class-wide types won't get rid of
those conversions, because they would be toward the root of the tree.
Another possible workaround would be to clutter the spec with an incomplete
type List before the nested package. But that could not be completed with
the private extension List, so we would still end up with the same problem.
To allow this approach, the Ada language would require modifications to allow
incomplete types to be completed by private type extensions.
with Ada.Iterator_Interfaces;
generic
type Element_Type is private;
package Ada.Containers.Doubly_Linked_Lists is
type List; --
package Cursors is
type Cursor is private;
No_Element : constant Cursor;
private
... --
end Cursors;
package Iterators is new
Ada.Iterator_Interfaces (Cursors, No_Element);
type List is new Iterators with private;
--
--
Empty_List : constant List;
function Is_Empty (Container : List) return Boolean;
procedure Clear (Container : in out List);
function Element (Position : Cursor)
return Element_Type;
...
private
... --
end Ada.Containers.Doubly_Linked_Lists;
Using the proposed syntax for private extension declarations however, these
issues do not arise. There is no need to create nested packages, incomplete
types, or complete an incomplete type with a private extension.
with Ada.Iterator_Interfaces;
generic
type Element_Type is private;
package Ada.Containers.Doubly_Linked_Lists is
type Cursor is private;
No_Element : constant Cursor;
package Iterators is private new
Ada.Iterator_Interfaces (Cursor, No_Element);
type List is new Iterators.Basic_Iterator with private;
Empty_List : constant List;
function Is_Empty (Container : List) return Boolean;
procedure Clear (Container : in out List);
function Element (Position : Cursor)
return Element_Type;
overriding function First (Container : List) return Cursor;
overriding function Next (Container : List; Position : Cursor) return Cursor;
private
... --
end Ada.Containers.Doubly_Linked_Lists;
!example
Now lets look at how the proposed syntax could be applied to the
examples in the problem section of this AI.
Here is the generic signature example:
generic
type Elem is private;
with function Hash (El : Elem) return Integer;
package Hashed_Sets is type Set is private;
function Union(Left, Right : Set) return Set;
function Size(Of_Set : Set) return Natural is <>;
function Intersection(Left, Right : Set) return Set is <>;
function Empty return Set is <>;
function Unit_Set(With_Item : Element) return Set is <>;
function Nth_Element(Of_Set : Set) return Element is <>;
package Signature is new private Set_Signature (Elem, Set);
private
type Set is record ... end record;
package Signature is new Set_Signature(Elem, Set);
end Hashed_Sets;
Here is an example where we want to instantiation a container:
package Expressions is
type Expr_Ref is private;
package Expr_Sequences is private new Sequences(Expr_Ref);
function Operands (Expr : Expr_Ref) return access Expr_Sequences.Sequence;
...
private
type Expression; --
type Expr_Ref is access Expression;
package Expr_Sequences is new Sequences(Expr_Ref);
end Expressions;
Note that being able to access the instantiation in the visible part
with a partial view provides a simple solution without having to introduce
incomplete types, derived types, or even named access types. Note with this
proposal we also have the option of having the Operands function return an
Expr_Sequences.Sequence instead of access Expr_Sequences.Sequence.
!ACATS test
ACATS B and C-Test(s) are necessary.
!appendix
From: Brad Moore
Date: Saturday, June 13, 2009 2:40 PM
>> This proposal does not "feel right" to me.
>
> Let me add that the reasons are not that obvious and surely this is no
> reflection on the author of the proposal. (Part of the reason for
> complaining now is the hope that Tucker or I or someone will have an
> inspiration before the next meeting, that won't happen if the proposal
> is thought to be "good enough".)
I agree with this sentiment. The proposal does not "feel right" to me either.
The whole business of requiring the declaration of a nested package, then
invoking (new) machinery to make it appear that we didn't declare a nested
package feels like a kludge to me.
I decided to rework the proposal I had submitted for AI05-0074-4.
I would like to call this a new proposal however (AI05-0074-5?), rather than an
update on the old proposal, because I have significantly reduced what I had
before, and made significant changes to simplify the semantic model. In
particular, I eliminated deferring actuals to the private part, and eliminated
the concept of a private instantiation declaration declaring a full view and a
partial view of an instance.
In Tallahassee I recall a conversation with Steve Baird where he was asking me
to explain my proposal. A couple of sentences into the description, I realized
what I was proposing was overly complex.
I will now attempt to describe the new model here in simplest terms.
- A private instantiation declaration occurs in the visible part of a package
and is completed by a generic instantiation in the private part of a package.
- A generic instantiation declares a view of an instance, and if a private
instantiation declaration is associated with a generic instantiation, then it
declares another (forward) view of the same instance.
- Both views of an instance are equivalent semantically, however there are
restrictions that apply to the use of the private instantiation declaration
within the immediately enclosing declarative region, before the completion in
the private part. To outside program units, these restrictions do not apply.
- The restrictions that apply only to the immediately enclosing declarative
region before the completion are;
a) Composite types (except array types whose components are not
composite types) exported from the view of the instance may
not directly appear as a component in a declaration.
b) Calls (function calls) may not be made to the generic
instantiation.
c) Objects or expressions involving objects of visible types
exported from the view of the instance are not allowed.
Now looking at Randy's example, we find that his issues do not arise. There is
no need to declare a nested package. No need to declare anonymous type that is
completed with a private extension (Not currently supported in the language). No
need to invoke special machinery (the proposed integrated package solution) to
make it appear that the nested package is not nested. There are less changes
needed to the existing container libraries.
generic
type Cursor is private;
No_Element : in Cursor;
package Ada.Iterator_Interfaces is
type Basic_Iterator is limited interface;
function First
(Object : Basic_Iterator) return Cursor is abstract;
function Next (Object : Basic_Iterator; Position : Cursor)
return Cursor is abstract;
end Ada.Iterator_Interfaces;
with Ada.Iterator_Interfaces;
generic
type Element_Type is private;
package Ada.Containers.Doubly_Linked_Lists is
type Cursor is private;
No_Element : constant Cursor;
package Iterators is private new
Ada.Iterator_Interfaces (Cursor, No_Element);
type List is new Iterators.Basic_Iterator with private;
Empty_List : constant List;
function Is_Empty (Container : List) return Boolean;
procedure Clear (Container : in out List);
function Element (Position : Cursor)
return Element_Type;
overriding function First (Container : List) return Cursor;
overriding function Next (Container : List; Position : Cursor) return Cursor;
private
... -- Not specified by the language
end Ada.Containers.Doubly_Linked_Lists;
The new AI proposal is attached. I would encourage reading of the discussion
section, because it shows simple comparative examples using the new nested
package approach vs the private instantiation declaration approach. In each
case, I find that the private instantiation is easier to read and understand.
One issue that has not been addressed with the integration package solution, is
that type derivations are often necessary, and if the type involved is a
synchronous tagged type, you are out of luck, since derivations of synchronous
tagged types are not currently allowed. If we do end up deciding to go with the
integrated package solution, it would at least be good to know how we might plan
to address this problem. The private instantiation solution does not require a
solution for this issue, because it avoids the need to create junk derivations.
The example I give in my proposal is a case where we want to have a type that
returns an access to an instantiation, where the instantiation involves a
synchronous tagged type.
In this example, we instantiate a generic with an object of a limited interface
representing a work item, and the generic gives us a worker task to operate on
the item.
package Work_Item is
type Job is limited interface;
procedure Analyze (Item : in out Job) is null; end Work_Item;
generic
type Work is limited new Job with private;
package Work_Item.Worker is
task type Periodic_Worker is new Job with
...
end Periodic_Worker;
end Work_Item.Worker;
We would like to have Get_Worker as a primitive function of our new type, but we
cant, since that requires a derivation of a synchronous tagged type. We end up
having to make Get_Worker a non-primitive function using current Ada 2005
syntax.
with Work_Item.Worker;
package Pkg is
type Worker_Task; -- Needed for nested Get_Worker call
type Worker_Access is access Worker_Task;
package Nested is
type T is limited new Work_Item.Job with private;
overriding procedure Analyze (Item : in out T);
function Get_Worker (Item : T) return Worker_Access;
private
type T is limited new Work_Item.Job with
record
...
end record;
end Nested;
use Nested;
package T_Worker is new Work_Item.Worker (Work => T);
type Worker_Task is new T_Worker.Periodic_Worker with null record;
-- ILLEGAL: Cannot derive a new type from a synchronous tagged type
-- Instead we have to declare a non-primitive function
function Get_Worker (Item : T) return access
T_Worker.Periodic_Worker;
end Pkg;
Using the proposed private instantiation syntax, we can do what we want to do.
If we decide we want the Get_Worker primitive to return a task instead of an
access to task, simply delete the word access in the following example.
with Work_Item.Worker;
package Pkg is
type T is limited new Work_Item.Job with private;
overriding procedure Analyze (Item : in out T);
package T_Worker is PRIVATE new Work_Item.Worker (Work => T);
function Get_Worker (Item : T) return access T_Worker.Periodic_Worker;
private
type T is limited new Work_Item.Job with
record
...
end record;
package T_Worker is new Work_Item.Worker (Work => T); end Pkg;
A question I have about the integrated package solution is, other than the need
to instantiate generics with private types, how likely is this new language
feature to be used? What other sorts of situations are there where this feature
would be useful?
****************************************************************
From: Bob Duff
Date: Wednesday, June 17, 2009 4:16 PM
I agree with your decision to make this a separate alternative.
But I still prefer the "use package" thing, perhaps with some modifications,
over the special instantiation idea.
For one thing, the "use package" thing solves more problems -- not just the
instantiation-related ones. The "use package" thing also allows some
interesting ways of composing abstractions (and allows "mis"use, but I don't
care about that).
****************************************************************
From: Brad Moore
Date: Saturday, June 27, 2009 2:26 PM
True, but also note that the most recent incarnation of the special
instantiation idea does solve problems that the "use package" thing doesn't seem
to solve.
For example, it gives you the ability to declare primitive subprograms that have
formals or results of an object of the instantiation. (as opposed an access type
designating an object of the instantiation) That seems useful to me.
People who are "use adverse" may not want to use this "use package" thing (for
the same reasons they don't use the existing "use"), and therefore the feature
may not address the instantiation problem for all the Ada community, whereas I
unable to imagine a reason why people would become "special-instantiation
adverse".
I am also not sure I see all the new "interesting ways of composing
abstractions".
For example, I'm not sure how often this feature would be needed for regular
(non-instance) nested packages, since the special instantiation idea eliminates
one of the main reasons for having to declare nested packages in the first
place. Otherwise, when I've needed to create nested packages (pretty rarely), is
when I have several variations of an abstraction. I can only think of examples
where we used this in Ada 83 code, as Ada 95 child packages eliminates a lot of
the need to declare nested packages I think.
As for declaring non-primitive constructor type functions, I think I would tend
to not use this feature there either as a matter of style. Using a Nested
package name such as "Constructors" or "Non_Dispatching", makes it more clear to
me that the subprogram is non-dispatching.
eg. Foo := Pkg.Constructors.Create;
The real benefit I see is that it allows you to present declarations of an
instantiation as though they were declared locally in a package spec,
potentially eliminating rename declarations. This doesn't sound like a "new way"
to me, it sounds more like a nicer, more convenient shorthand for an "old way".
****************************************************************
From: Robert Dewar
Date: Saturday, June 27, 2009 2:59 PM
> People who are "use adverse" may not want to use this "use package"
> thing (for the same reasons they don't use the existing "use"), and
> therefore the feature may not address the instantiation problem for
> all the Ada community, whereas I unable to imagine a reason why people
> would become "special-instantiation adverse".
a) I don't think the language design should pay attention to peculiar
adverse reactions of this type.
b) I don't think you can possibly guess what strange adverse reactions
people wil acquire.
Specifically, the use adverse crowd likes to require explicit qualification of
all references, but this principle is badly broken for derived types anyway, so
that object oriented code routinely has references to a variable Clunk with no
explicit declaration of Clunk in sight, even if no use clauses are present. Yes,
there is an implicit declaration and a tool could point you to this implicit
declaration, but once you depend on such tools, the whole motivation for
explicit qualification is dubious if you ask me.
I somewhat share the preference for the "use package" approach, though I do
understand what you are saying (that so far at least this is not as expressive).
[Editor's note: The rest of the "use-averse" thread is filed in AI05-0150-1, as
it has more to do with use clauses than this proposal.]
****************************************************************
Questions? Ask the ACAA Technical Agent