Version 1.6 of ais/ai-00391.txt

Unformatted version of ais/ai-00391.txt version 1.6
Other versions for file ais/ai-00391.txt

!standard 03.09.03 (04)          04-12-09 AI95-00391/04
!standard 03.04 (27)
!standard 03.09.01 (04)
!standard 03.09.03 (06)
!class amendment 04-11-13
!status Amendment 200Y 04-12-03
!status ARG Approved 5-0-5 04-11-20
!status work item 04-11-13
!status received 04-11-13
!priority Medium
!difficulty Medium
!subject Functions with controlling results in null extensions
!summary
If a type extension is a record extension with a null extension part, and no new discriminant part, then primitive functions with controlling results do not need to be overridden. The result of calling an implicitly declared inherited function with a controlling result is an extension aggregate with a null extension part. Note that functions with controlling access results still need to be overridden.
!problem
type derivation is commonly used after a package instantation that declares a new type, to redeclare the new type in the scope enclosing the instantiation.
For example
package T_Sets is new Sets(T); type T_Set is new T_Sets.Set;
Unfortunately, this paradigm does not work if the type is tagged and has one or more functions with controlling results. For example, this does not work:
generic type Element is private; package Sets is type Set is tagged private; function Unit_Set(E : Element) return Set; ... end Sets;
package T_Sets is new Sets(T); type T_Set is new T_Sets.Set with null record; -- This is illegal unless Unit_Set is overridden here
This is unpleasant. The programmer just wants to use the type, but they want it declared in the current scope, rather than inside the instantiation. Because of this, they are forced to override each of the functions with controlling results even though they have not added any components nor changed the type in any significant way.
!proposal
There are various possible solutions. One would be a kind of "type rename" clause which would implicitly declare renames of all of the primitive subprograms of the type as well.
However, that wouldn't address the more general issue of a null extension of a type, and the unnecessary burden of having to override all of the functions with controlling results.
The more straightforward solution is to drop the requirement to override functions with controlling results when the derived type is a "null extension" -- no components in the extension part, and no new discriminant part. One could take it further to allow a new discriminant part, so long as the discriminants are associated one-for-one with the existing discriminants. It's not clear that this last capability would be worth the effort, so we haven't included it here. It would clearly be feasible to add this capability at a later date, or during ARG review.
!wording
Modify 3.4(27) as follows:
... If the result type of the inherited subprogram is the derived type, the result of calling the parent's subprogram is converted to the derived type{, or in the case of a null extension, extended to the derived type using the equivalent of an extension_aggregate with the original result as the ancestor_part and NULL RECORD as the record_component_association_list}.
Add after 3.9.1(4):
Static Semantics
A record extension is a null extension if its declaration has no known_discriminant_part and its record_extension_part includes no component_declarations.
Modify 3.9.3(4):
For a derived type, if the parent or ancestor type has an abstract primitive subprogram, {has a primitive function with a controlling access result,} or {, for a derived type other than a null extension, has} a primitive function with a controlling result, then:
NOTE: The mention of "controlling access result" above presumes an AI still in development to fix AI-318-2.
Modify 3.9.3(6):
* Otherwise, the subprogram shall be overridden with a nonabstract subprogram {or, in the case of a private extension inheriting a function with a controlling result, have a full type that is a null extension}; ...
!discussion
This problem seems important to fix, especially as we are defining standard "container" packages which declare tagged types. Having to override functions with controlling results for null extensions is a nuisance.
One could go farther and eliminate the requirement for overriding functions with controlling results if all new components had explicit defaults, but that seems a potential source of errors. With null extensions, the likelihood of an automatically provided overriding being inappropriate seems miniscule.
Although we don't talk about a "wrapper," we anticipate that most implementations will need to provide them, since the caller will not be aware they need to change the tag of the result returned by the function when the function is reached via a dispatching call. Clearly the wrapper will be trivial, analogous to the kind of wrapper often created for a renaming-as-body. A null extension aggregate essentially just changes the tag of its ancestor_part.
We do not provide automatic null extensions for functions with controlling access results, as this would imply allocating a new object and copying into it. We can't just change the tag in place because the access value might refer to a preexisting object. We also can't copy in the case of a limited designated type.
No corresponding problem exists for untagged types, because derived types with primitives are required to have the same representation. Note that this sort of conversion between access T and access NT occurs on the way "in" to an inherited primitive of an untagged type with an access parameter, so performing the reverse conversion on the return value should impose no new implementation issue.
!example
(See problem.)
!corrigendum 3.4(27)
Replace the paragraph:
For the execution of a call on an inherited subprogram, a call on the corresponding primitive subprogram of the parent type is performed; the normal conversion of each actual parameter to the subtype of the corresponding formal parameter (see 6.4.1) performs any necessary type conversion as well. If the result type of the inherited subprogram is the derived type, the result of calling the parent's subprogram is converted to the derived type.
by:
For the execution of a call on an inherited subprogram, a call on the corresponding primitive subprogram of the parent type is performed; the normal conversion of each actual parameter to the subtype of the corresponding formal parameter (see 6.4.1) performs any necessary type conversion as well. If the result type of the inherited subprogram is the derived type, the result of calling the parent's subprogram is converted to the derived type, or in the case of a null extension, extended to the derived type using the equivalent of an extension_aggregate with the original result as the ancestor_part and null record as the record_component_association_list.
!corrigendum 3.9.1(4)
Insert after the paragraph:
A type extension shall not be declared in a generic body if the parent type is declared outside that body.
the new paragraph:
Static Semantics
A record extension is a null extension if its declaration has no known_discriminant_part and its record_extension_part includes no component_declarations.
!corrigendum 3.9.3(4)
Replace the paragraph:
For a derived type, if the parent or ancestor type has an abstract primitive subprogram, or a primitive function with a controlling result, then:
by:
For a derived type, if the parent or abstract type has an abstract primitive subprogram, has a primitive function with a controlling access result, or, for a derived type other than a null extension, has a primitive function with a controlling result, then:
!corrigendum 3.9.3(6)
Replace the paragraph:
Otherwise, the subprogram shall be overridden with a nonabstract subprogram; for a type declared in the visible part of a package, the overriding may be either in the visible or the private part. However, if the type is a generic formal type, the subprogram need not be overridden for the formal type itself; a nonabstract version will necessarily be provided by the actual type.
by:
Otherwise, the subprogram shall be overridden with a nonabstract subprogram or, in the case of a private extension inheriting a function with a controlling result, have a full type that is a null extension; for a type declared in the visible part of a package, the overriding may be either in the visible or the private part. However, if the type is a generic formal type, the subprogram need not be overridden for the formal type itself; a nonabstract version will necessarily be provided by the actual type.
!ACATS test
ACATS tests need to be constructed to test this feature.
!appendix

From: Dan Eilers
Sent: Monday, November 15, 2004  1:54 PM

> There are various possible solutions.  One would be a kind
> of "type rename" clause which would implicitly declare renames
> of all of the primitive subprograms of the type as well.

I'd like to expound a bit on this "type rename" idea, since it
solves a broader class of problems, and is therefore a potentially
viable idea whether or not Tuck's proposed solution is implemented.

Derived types are intended to serve two purposes:  1) the Ada83 purpose
of creating two distinct types (e.g., apples and oranges) that are
incompatible although they have common representation and operations;
and 2) the Ada95 purpose of type extension.

However, derived types are commonly abused to serve the purpose
of making a type declaration accessible outside the package in
which it is declared.  This purpose was supposed to be served
by type renaming, as stated in Steelman requirement 3-5B:
"Definitions that are made within an encapsulation and are externally
accessible may be renamed before use outside the encapsulation."

Apparently, type renaming was omitted from Ada as an economy measure,
under the delusion that the same effect could be achieved via a
constraint-less subtype declaration, as is claimed in RM 8.5(6).
This claim is not completely true, since subtype declarations cannot
be used to complete a private type declaration.

The need for type renaming to provide the completion for a
private type declaration stems from another unmet Steelman
requirement, generic types, as stated in Steelman requirement 12D:
"12D. Generic Definitions. Functions, procedures, types, and
encapsulations may have generic parameters."

If generic types were available (or class-like packages that
exported a type), a private type could often be completed directly
as the instantiation of a generic type (or class, resp), with no
need for either type derivation or type renaming.  Instead, the user
must enclose such a type in a generic package and instantiate that
package, creating the need to somehow reach into the instantiated
package when completing the private type.

For example, we would like to say:

  with sets;
  package p1 is
     type int_set is private;
  private
     type int_set is new sets(integer);  -- illegal, no generic types
  end p1;

Instead we say:

  with sets;
  package p2 is
     type int_set is private;
  private
     package int_sets_pkg is new sets(integer);
     -- subtype int_set is int_sets_pkg.set;    -- illegal use of subtype
     -- type int_set renames int_sets_pkg.set;  -- illegal, no type renaming
     type int_set is new int_sets_pkg.set;      -- OK, but abuse of derivation
  end p2;

This use of derivation to achieve type renaming is an abuse because
the user doesn't want two distinct types, int_set and int_sets_pkg.set.
Instead, the user wants int_set to be the same type as int_sets_pkg.set,
just with a different name and scope.  A reader/maintainer is forced to
consider whether two distinct types are being created on purpose, or
only as an unwanted side effect.

The users also wants the type renaming to create renames of the primitive
operations, so these operations are also visible in the scope of int_set.
This is analogous to the way derived types work, only simpler, since
there is no need for implicit conversions of operands and/or result types.

As Tuck explains, this abuse of derivation causes a further complication
when the type has one or more functions with controlling results, because
such functions must additionally be overriden, even if they are never used.

Fixing this need to overide such functions would make derived types
somewhat somewhat more palatable as a substitute for type renaming, but
it would still be an abuse.

Adding type renaming would satisfy the original Steelman requirement 3-5B.
It would improve understanding of the language, eliminating the silly
restriction that other definitions can be renamed, just not types.
The use of renaming for type completion would be directly analogous
to the use of renaming-as-body for subprogram completion, and therefore
easy to understand.  Type renaming used for completing private types
would improve the readability of programs, since it prevents creation
of an unwanted incompatible type.

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

From: Tucker Taft
Sent: Monday, November 15, 2004  3:17 PM

I think type renames are interesting, but too big a change
at this point.  I am also made very nervous by a private
type completed by a type rename, since you now have two
distinct scopes where you could define primitives of
the type, one where the type was originally defined, and
one where it was renamed.  I had imagined a type rename
more like a combination of a subtype declaration and a
set of renames of the primitive subprograms.  That would
solve the problem of making the type and its primitives
visible, but it wouldn't allow you to complete a private
type, nor add more primitives at the point of the renaming.

I don't know how you weight the advantages of making the
type and its primitives visible versus the ability to
complete a private type.  I don't see much alternative
to the type derivation model if you are completing a
private type, because of the possibility of adding more
primitives.  Since the type derivation model has been
in use for the past 20 years, it seems hard to justify
introducing a feature with so much overlapping functionality
at this time.  In any case, as I said above, this seems way too
much for a last minute "quick fix."

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

From: Dan Eilers
Sent: Monday, November 15, 2004  8:15 PM

>                 I am also made very nervous by a private
> type completed by a type rename, since you now have two
> distinct scopes where you could define primitives of
> the type, one where the type was originally defined, and
> one where it was renamed.

Yes, this makes me nervous too, now that you point it out.
But in the case I'm trying to get to work (where a private type
is completed by renaming a type created by an instantiation),
the user doesn't intend to ever again reference the type
created by the instantiation.  So there may be a solution
if this can be enforced.

Here's a new proposal:

Instead of the current way of:
     package int_sets_pkg is new sets(integer);
     type int_set is new int_sets_pkg.set;

or my proposed way of:

     package int_sets_pkg is new sets(integer);
     type int_set renames int_sets_pkg.set;

do something like:

     type int_set is new sets(integer).set;

where sets(integer) is an anonymous instantiation.
Since the instantiation is anonymous, there is no
possibility of the user ever subsequently referencing
the instantiation or types within it.

> I don't know how you weight the advantages of making the
> type and its primitives visible versus the ability to
> complete a private type.

I'm most concerned with completing a private type without
out needing derived types, preferably in a way that
resembles instantiation of generic types/classes.

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

From: Tucker Taft
Sent: Monday, November 15, 2004  8:39 PM

The idea of anonymous instantiations was floated during
the Ada 9X process, with something pretty close to
the syntax Dan proposes.  We didn't pursue it, but
I don't know all the reasons.  I do know it seems
like a lot of work just to avoid using a type
derivation.  And I think behind the scenes the
implementor would have to do a type derivation to
avoid a huge implementation effort.  I can also
imagine that handling anonymous instantiations
might involve some pain in the debugger, etc.,
dealing with stepping through anonymous instantiations,
coming up with unique link names, etc.

Based on the C++ experience, anonymous instantiations
are generally a pain, and the gain comes primarily from
*implicit* instantiations for generic subprograms,
something which is a very big language design
problem, and I suspect also a big implementation
problem.

Now if we were talking about adding *iterators* I
know a few ARG members who might get excited, but
anonymous instantiations doesn't do it, at least
not for me.

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

From: Randy Brukardt
Sent: Monday, November 15, 2004  7:21 PM

I'm working on the Abstract Types section today, and one of the AARM notes
caused me to think a bit about Tucker's proposal. As written, it allows the
creation of objects of an abstract type, which is a no-no.  Consider:

  package P1 is
    type Something is tagged record ...;
    function Constructor return Something;
    type Acc_All is access all Something'Class;
  end P1;

  with P1;
  package P2 is
    type Tucker_Type is abstract new P1.Something with null record;
    -- Illegal in Ada 95, legal and inherits a concrete function Constructor
    -- with Tucker's proposal.
    Ptr : P1.Acc_All := new Tucker_Type'Class (Tucker_Type'(Constructor));
       -- Now we have an object of an abstract type, with the tag of the
       -- abstract type.
  end P2;

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

From: Robert A. Duff
Sent: Monday, November 15, 2004  7:21 PM

Hmm.  I think (I'm leaving out irrelevant words) "the type of an object
created by an allocator shall not be abstract" at 3.9.3(8) saves us
here.  I.e. the above is still illegal, as it should be.

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

From: Randy Brukardt
Sent: Monday, November 15, 2004  7:45 PM

I don't think that applies: class-wide types are never abstract, and that's
the type of the object that we're creating. Even if it does apply, but there
are other ways to make such a call. Try:

    procedure Ugh (Obj : in Tucker_Type'Class);

    Ugh (Constructor); -- Tag-indeterminant call.

Ugh could have the allocator, and there'd be no way for a legality rule to
check it there!

The problem is having a concrete function returning an abstract object. Once
you have that, there's always a way to get that into an object. Clearly,
Tucker's AI will need to avoid including them in his "null extension"
definition.

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

From: Tucker Taft
Sent: Monday, November 15, 2004  8:18 PM

Bob's comment may save us, but it would be simpler
to just fix the suggested wording of the AI to say:

Modify 3.9.3(4) of AI-251:

    If a type inherits a subprogram corresponding to an abstract
    subprogram{, or a type other than a NONABSTRACT null extension
    inherits} [or to] a function with a controlling result, then:

Modify 3.9.3(4):

    * Otherwise, the subprogram shall be overridden with a nonabstract
      subprogram {or, in the case of a private extension inheriting
      a NONABSTRACT function with a controlling result, have a full type
      that is a null extension}; ...

The added word here is "NONABSTRACT" in both sentences.

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


Questions? Ask the ACAA Technical Agent