Version 1.4 of ais/ai-00287.txt

Unformatted version of ais/ai-00287.txt version 1.4
Other versions for file ais/ai-00287.txt

!standard 7.5 (00)          02-10-09 AI95-00287/01
!class amendment 02-02-06
!status work item 02-10-09
!status received 02-02-06
!priority Medium
!difficulty Medium
!subject Limited Aggregates Allowed
!summary
limited objects may be initialized. limited aggregates are allowed.
Subtype_marks are allowed in place of expressions in component_associations of aggregates, with the same meaning as when a subtype_mark is used as the ancestor_part in an extension_aggregate.
!problem
Ada's limited types allow programmers to express the idea that "copying values of this type does not make sense". This is a very useful capability; after all, the whole point of a compile-time type system is to allow programmers to formally express which operations do and do not make sense for each type.
Unfortunately, Ada places certain limitations on limited types that have nothing to do with the prevention of copying. The primary example is aggregates: the programmer is forced to choose between the benefits of aggregates (full coverage checking) and the benefits of limited types. Forcing programmers to choose between two features that ought to be orthogonal is one of the most frustrating aspects of Ada.
The full coverage rules (for aggregates and case statements) are often considered to be one of the primary benefits of Ada over many other languages, especially with type extensions, where some components are inherited from elsewhere.
Another important benefit of Ada is the ability to initialize an object when it is created by an object_declaration or allocator. It is a shame that the capability is currently unavailable for limited types. Moreover, the lack of this capability makes it impossible to declare constant limited objects.
!proposal
(See Wording.)
!wording
Delete "nonlimited" from 4.3(1), 4.3.1(8), twice in 4.3.2(4), 4.3.3(7). These are the rules that require aggregates to be nonlimited.
The following rules forbid initialization of the various kinds of limited objects. Delete them. (Note that initialization of components of limited aggregates was forbidden by consequence of the fact that limited aggregates were forbidden, and the fact that a limited component type makes the containing type limited).
3.3.1(5): An initialization expression shall not be given if the object is of a limited type.
3.8(8): A default_expression is not permitted if the component is of a limited type.
4.8(5): If the designated type is limited, the allocator shall be an uninitialized allocator.
12.4(8): The type of a generic formal object of mode in shall be nonlimited.
Replace the above deleted rules with a single Legality Rule just before 7.5(2):
For an assignment operation that initializes an object with the value of a limited expression, the expression shall be one of the following:
- an aggregate - a qualified_expression whose expression is one of these - a parenthesized_expression whose expression is one of these
AARM annotation:
Ramification: These are the forbidden assignment operations, where X is the name of a limited object:
- Object: T := X; -- initialization expression
- record Component: T := X; -- default_expression end record;
- (Component => X) -- the expression of a component_association of an aggregate
- (X with ...) -- an expression used as the ancestor_part of an extension_aggregate
- new T'(X) -- initialization of an allocated object
- generic Formal: T; package P is ... end;
package Q is new P(Formal => X); -- actual for generic formal object of mode 'in'
if X were replaced by an aggregate in the examples above, they would be legal.
An assignment operation is always part of an assignment_statement or the initialization of an object. Assignment_statements are already forbidden for limited types, so this rule need only mention initializations.
Just after 7.5(8), add:
Implementation Requirements
For an aggregate of a limited type used to initialize an object as allowed above, the implementation shall not create a separate anonymous object for the aggregate. The aggregate shall be constructed directly in the new object.
----------------
The introductory paragraph of 7.5 currently says:
7.5(1): A limited type is (a view of) a type for which the assignment operation is not allowed. A nonlimited type is a (view of a) type for which the assignment operation is allowed.
It doesn't really say anything; it's just introductory verbiage, and is bracketed in the AARM. We can replace it with:
7.5(1): A limited type is (a view of) a type for which copying (such as for an assignment_statement) is not allowed. A nonlimited type is a (view of a) type for which copying is allowed.
The NOTES in 7.5(9-15) are now all wrong. The simplest thing would be to delete them.
In the NOTE at 3.6.2(16), delete ", unless the array type is limited."
In the NOTE at 3.8(25), delete ", unless the record type is limited."
The NOTEs at 7.3.1(12), 9.1(21), and 9.4(23) also need minor rewording.
----------------
4.3.1(4) says:
record_component_association ::=
[ component_choice_list => ] expression
Modify this to allow a subtype_mark in place of the expression:
4.3.1(4):
record_component_association ::=
[ component_choice_list => ] expression
| [ component_choice_list => ] subtype_mark
4.3.1(10) says:
The expected type for the expression of a record_component_association is the type of the associated component(s); the associated component(s) are as follows:
Replace that with:
For a record_component_association, the associated component(s) are as follows:
After 4.3.1(13), add:
For a record_component_association with an expression, the expected type for the expression is the type of the associated component(s). For a record_component_association with a subtype_mark, the subtype_mark shall statically denote the subtype of the associated component(s).
After 4.3.1(17), add:
A record_component_association for a discriminant shall have an expression rather than a subtype_mark.
AARM Annotation:
Reason: Otherwise, one could construct records with undefined discriminant values.
After 4.3.1(19), add:
For a record_component_association with an expression, the expression defines the value for the associated component(s). For a record_component_association with a subtype_mark, the associated component(s) are initialized by default (see 3.3.1).
!discussion
The basic idea is that there is nothing wrong with constructing a limited object; copying is the evil thing. One should be allowed to create a new object (whether it be a standalone object, or a formal parameter in a call, or whatever), and initialize that object with an aggregate. In implementation terms, the aggregate, is built in place in its final destination -- no copying is necessary, or allowed.
Note: Another AI generalizes this idea to allowing function calls in addition to aggregates.
limited constants were never directly disallowed; they were illegal because limited objects could not be initialized, whereas constants must be initialized. Now that limited objects can be initialized, limited constants are allowed.
Implementation of this AI is almost trivial. The compiler front end needs to relax its rules. There are no new run-time concepts: initializing in place is already required for controlled objects by 7.6(17.1/1), and the semantics of subtype_marks used in component associations is essentially the same as for subtype_marks used as the ancestor_part of an extension_aggregate.
limited types offer other advantages in addition to lack of copying: access discriminants, and the ability to take 'access of the "current instance". It seems a shame to require the programmer to choose between these and aggregates.
Note that copying of limited values is still illegal. For example:
T2: Some_Task_Type := T1;
will still be forbidden by the rule added just before 7.5(2).
Note that when a component of an aggregate is given by a subtype_mark, the value is not "assigned" into the component; the component is just default initialized. Thus, no constraint check is done.
The master associated with a limited aggregate is that of the object being initialized, so no anomalies related to finalization occur.
Question: Was it the intent of the ARG to allow components of *array* aggregates to be given by subtype_marks? I didn't allow that.
Question: I said the subtype_mark in a record_component_association has to "statically denote" the subtype of the component. is that OK? We had talked about a run-time check, before.
Compatibility:
This change is not quite upward compatible. Consider:
type Lim is limited record Comp: Integer; end record;
type Not_Lim is record Comp: Integer; end record;
procedure P(X: Lim); procedure P(X: Not_Lim);
P((Comp => 123));
The call to P is currently legal, and calls P(Not_Lim). In the new language, this call will be ambiguous.
This seems like a tolerable incompatibility. It is always caught at compile time, and cases where nonlimitedness is used to resolve overloading have got to be vanishingly rare. The above program, though legal, is highly confusing, and I can't imagine anybody wanting to do that. The current rule was a mistake in the first place: even if limited aggregates should be illegal, that should not be a Name Resolution Rule.
!example
type T is tagged limited record X: ...; Y: ...; Z: ...; end record;
type Ptr is access T'Class;
Object_1: constant T := (X => ..., Y => ..., Z => ...);
Object_2: Ptr := new T'(X => ..., Y => ..., Z => ...); -- Build a limited object in the heap.
!ACATS test
!appendix

From: Robert Duff
Sent: Wednesday, February 6, 2002,  6:05 PM

                    Limited Types Considered Limited

One of my homework assignments was to propose changes to "fix" limited
types. This e-mail outlines the problems, and proposed solutions.
I haven't written down every detail -- I first want to find out if there
is any interest in going forward with these changes. Some of these
ideas came from Tucker.

I apologize for doing my homework at the last minute. I hope folks will
have a chance to read this before the meeting, and I hope Pascal is
willing to put it on the agenda.

Ada's limited types allow programmers to express the idea that "copying
values of this type does not make sense". This is a very useful
capability; after all, the whole point of a compile-time type system is
to allow programmers to formally express which operations do and do not
make sense for each type.

Unfortunately, Ada places certain limitations on limited types that have
nothing to do with the prevention of copying. The primary example is
aggregates: the programmer is forced to choose between the benefits of
aggregates (full coverage checking) and the benefits of limited types.
Forcing programmers to choose between two features that ought to be
orthogonal is one of the most frustrating aspects of Ada.

I consider the full coverage rules (for aggregates and case statements)
to be one of the primary benefits of Ada over many other languages,
especially with type extensions, where some components are inherited
from elsewhere. I will refrain from further singing the praises of full
coverage; I assume I'm preaching to the choir.

My goals are:

    - Allow aggregates of limited types.

    - Allow constructor functions that return limited types.

    - Allow initialization of limited objects.

    - Allow limited constants.

    - Allow subtype_marks in aggregates more generally.
      (They are currently allowed only for the parent part in an
      extension aggregate.)

The basic idea is that there is nothing wrong with constructing a
limited object; *copying* is the evil thing. One should be allowed to
create a new object (whether it be a standalone object, or a formal
parameter in a call, or whatever), and initialize that object with a
function call or an aggregate. In implementation terms, the result
object of the function call, or the aggregate, is built in place in its
final destination -- no copying is necessary, or allowed.

All of the above goals except constructor functions are fairly trivial
to achieve, both in terms of language design and in terms of
implementation effort. Constructor functions are somewhat more
involved. However, I am against any language design that allows
aggregates where function calls are not allowed; subprogram calls are
perhaps the single most important tool of abstraction ever invented!
(There is at least one other such case in Ada, and I hate it.)

By "constructor function", I mean a function that returns an object
created local to the function, as opposed to an object that already
existed before the function was called.

Ada currently allows functions to return limited types in two cases,
neither of which achieves the goal here:

    If the limited type "becomes nonlimited" (for example, a limited
    private type whose full type is integer), then constructor functions
    are allowed, but the return involves a copy, thus defeating the
    purpose of limited types. Anyway, this feature is not allowed for
    various types, such as tagged types.

    If the limited type does not become nonlimited, then it is returned
    by reference, and the returned object must exist prior to the
    function call; it cannot be created by the function. In essense,
    these functions don't return limited objects at all; they simply
    return a pointer to a preexisting limited object (or perhaps
    a heap object).

We need a new kind of function that constructs a new limited object
inside of itself, and returns that object to be used in the
initialization of some outer object. The run-time model is that the
caller allocates the object, and passes in a pointer to that object.
The function builds its result in that place; thus, no copying is done
on return.

Because the run-time model for calls to these constructor functions is
different from that of existing functions that return a limited type, we
need to indicate this syntactically on the spec of the function. In
particular, change the syntax of functions so that "return" can be
replaced by "out", indicating a constructor function. In addition,
change the syntax of object declarations to allow "out", as in
"X: out T;"; this marks the object as "the result object" of a limited
constructor function. The reason for "out" is that these things behave
much like parameters of mode 'out'.

Examples:

    type T is tagged limited
        record
            X: ...;
            Y: ...;
            Z: ...;
        end record;

    type Ptr is access T'Class;

    Object_1: constant T := (X => ..., Y => ..., Z => ...);

    function F(X: ...; Y: ...) out T;

    function F(X: ...; Y: ...) out T is
        Result: out T := (X => X, Y => Y, Z => ...);
    begin
        ... -- possible modifications of Result.
        return Result;
    end F;

    Object_2: Ptr := new T'(F(X => ..., Y => ...));
        -- Build a limited object in the heap.

Rules:

Change the rules in 4.3 to allow limited aggregates. This basically
means erasing the word "nonlimited" in a few places.

Change the rule in 3.3.1(5) about initializing objects to allow limited
types. But require the expression to be an aggregate or a constructor
function. ("X: T := Y;", where Y is a limited object, remains illegal,
because that would necessarily involve a copy.)  There are various
analogous rules (initialized allocators, subexpressions of aggregates,
&c) that need analogous changes.

Assignment statements remain illegal for limited types, even if the
right-hand side is an aggregate or limited constructor function.

Allowing constants falls out from the other rules.

Allow a component expression in an aggregate to be a subtype_mark. This
means that the component is created as a default-initialized object.
It's essentially the same thing we already allow in an extension
aggregate; we're simply generalizing it to all components of all
aggregates. This is important, in case some part of the type is
private. There is no reason to limit this capability to limited types.

Specify that limited aggregates are built "in place"; there is always a
newly-created object provided by the context. Note that we already have
one case where aggregates are built in place: (nonlimited) controlled
aggregates. Similarly, the result of a limited constructor function is
built in place; the context of the call provides a newly-created object.
(In the case of "X: T := F(...);", where F says "return G(...);", F will
receive the address of X, and simply pass it on to G.)

If the result object of a limited constructor function contains tasks,
the master is the caller.

For a function whose result is declared "out T", T must be a limited
type; such a function is defined to be a "limited constructor function".

Subtype T must be definite. This rule is not semantically necessary.
However, the run-time model calls for the caller to allocate the result
object, and this rule allows the caller to know its size before the
call. Without this rule, a different run-time model would be required
for indefinite subtypes: the called function would have to allocate the
result in the heap, and return a pointer to it. A design principle of
Ada is to avoid language rules that require implicit heap allocation;
hence this rule. (An alternative rule would be that T must be
constrained if composite, thus eliminating defaulted discriminants.)

A limited constructor function must have exactly one return statement.
The expression must be one of the following:

    - An object local to the function (possibly nested in block
      statements), declared with "out".

    - A function call to a limited constructor function.

    - An aggregate.

    - A parenthesized or qualified expression of one of these.

An object declared "out" must be local to a limited constructor
function.

A constraint check is needed on creation of a local "out" object.
We have to do the check early (as opposed to the usual check on the
return statement), because we need to make sure the object fits in the
place where it belongs (at the call site). If the return expression is
an aggregate, that needs a constraint check, as usual. If the return
expression is a function call, then that function will do whatever
checking is necessary.

Is there an issue with dispatching-on-result functions?
I don't think so.


Compatibility:

This change is not upward compatible. Consider:

    type Lim is limited
        record
            Comp: Integer;
        end record;

    type Not_Lim is
        record
            Comp: Integer;
        end record;

    procedure P(X: Lim);
    procedure P(X: Not_Lim);

    P((Comp => 123));

The call to P is currently legal, and calls P(Not_Lim). In the new
language, this call will be ambiguous.

This seems like a tolerable incompatibility. It is always caught at
compile time, and cases where nonlimitedness is used to resolve
overloading have got to be vanishingly rare. The above program, though
legal, is highly confusing, and I can't imagine anybody wanting to do
that. The current rule was a mistake in the first place: even if
limited aggregates *should* be illegal, that should not be a Name
Resolution Rule.


Other advantages:

One advantage of this change is that it makes the usage style of limited
types more uniform with nonlimited types, thus making the language more
accessible to beginners.

How do you construct an object in Ada?  You call a function. Cool -- no
need for the kludginess of C++ constructors. But if it's limited, you
have to fool about with discriminants -- not something that would
naturally occur to a beginner. And discriminants have various annoying
restrictions when used for this purpose.

How do you capture the result of a function call?  You put it in a
constant: "X: constant T := F(...);". But if it's limited, you have to
*rename* it: "X: T renames F(...);". Again, that's not something that
would naturally occur to a beginner -- and the beginner would rightly
look upon it as a "trick" or a "workaround".

Another point is that the current rules force you into the heap,
unnecessarily. You end up passing around pointers to limited objects,
either explicitly or implicitly, which tends to add complexity to one's
programs.

Limited types offer other advantages in addition to lack of copying:
access discriminants, and the ability to take 'Access of the "current
instance". It seems a shame to require the programmer to choose between
these and aggregates.


Alternatives:

It is not strictly necessary to mark the result object with "out"; the
compiler could deduce this information by looking at the return
statement(s). However, marking the object simplifies the compiler -- it
needs to treat this object specially by using the space allocated by the
caller.

It is not necessary to limit the number of return statements to 1.
However, it seems simplest. We need to prevent things like this:

    function F(...) out T is
        Result_1: out T;
        Result_2: out T;
    begin
        Result_1.X := 123;
        Result_2.X := 456;
        if ... then
            return Result_1;
        else
            return Result_2;
        end if;
    end F;

because we can't allocate Result_1 and Result_2 in the same place!
On the other hand, the following could work:

    function F(...) out T is
        Result: out T;
    begin
        if ... then
            return Result;
        else
            return Result;
        end if;
    end F;

suggesting a rule that all return statements must refer to the *same*
object. But this could work, too:

    function F(...) out T is
    begin
        if ... then
            return (...); -- aggregate
        elsif ... then
            return G(...); -- function call
        elsif ... then
            declare
                Result_1: out T;
            begin
                Result_1.X := 123;
                return Result_1; -- local
            end;
        else
            declare
                Result_2: out T;
            begin
                Result_2.X := 456;
                return Result_2; -- different local
            end;
        end if;
    end F;

because only one of the four different result objects exists at any
given time. I'm not sure how much to relax this rule. Perhaps some
rule about declaring only one of these special result objects in a given
region?

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

From: Robert Dewar
Sent: Thursday, February 7, 2002,  9:02 AM

I must say that for me, this entire proposal seems to be insufficiently
grounded in real requirements. I am concerned that the ARG is starting to
wander around in the realm of nice-to-have-neat-language-extensions which
are really rather irrelevant to the future success of Ada. I am not opposed
to a few extensions in areas where a really important marketplace need has
been demonstrated, but the burden for new extensions should be extremely
high in my view, and this extension seems to fall far short of meeting
that burden.

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

From: Randy Brukardt
Sent: Thursday, February 7, 2002,  2:19 PM

I hate to be agreeing with Robert here :-), but he's right.

There is a problem worth solving here (the inability to have constants of
limited types), but that could adequately be solved simply by the 'in-place'
construction of aggregates (which we already require in similar contexts).
[I'll post a real-world example of the problem in my next message.] The problem
is relatively limited, and thus the solution also has to be limited, or it
isn't worth it. This whole business of constructor functions only will sink any
attempt to fix the real problem, because it is just too big of a change at this
point.

Bob's concerns about the purity of the language would make sense in a new
language design, but we're working with limited resources here, and simple
solutions are preferred over perfect ones.

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

From: Randy Brukardt
Sent: Thursday, February 7, 2002,  3:05 PM

Here is an example that came up in Claw where we really wanted constants of a
limited type:

The Windows registry contains a bunch of predefined "keys", along with user
defined keys. Our original design for the key type was something like (these
types were all private, and the constants were deferred, but I've left that out
for clarity):

    type Key_Type is new Ada.Finalization.Limited_Controlled with record
      Handle : Claw.Win32.HKey := Claw.Win32.Null_HKey;
      Predefined_Key : Boolean := False; -- Is this a predefined key?
                                         -- (Only valid if Handle is not null)
      -- other components.
    end record;

    Classes_Root : constant Key_Type := (Ada.Finalization.Limited_Controlled with
       Handle => 16#80000000#, -- Windows magic number
       Predefined_Key => True, ...);
    Current_User : constant Key_Type := (Ada.Finalization.Limited_Controlled with
       Handle => 16#80000001#, -- Windows magic number
       Predefined_Key => True, ...);
    -- And several more like this.

    procedure Open (Key    : in out Key_Type;
                    Parent : in Key_Type;
                    Name   : in String);

    procedure Close (Key   : in out Key_Type);

    procedure Put (Root_Key   : in Key_Type;
                   Subkey     : in String;
                   Value_Name : in String;
                   Item       : in String);

    -- and so on..

Of course, our favorite compiler rejected the constants as illegal.

So, they were turned into functions.

   function Classes_Root return Key_Type;
   function Current_User return Key_Type;

However, these have the problem that they have to be overridden for any
extensions of the type (as they are primitive). We could have put them into a
child/nested package (to make them not primitive), but that would bend the
structure of the design even further and add an extra package for no good
reason. We also could have made them class-wide, but that would be a misleading
specification, as they can never return anything other than Key_Type. So we
left them in the main package.

Aside: we originally wanted to use these as default parameters for some of the
various primitive routines. However, that would illegal by 3.9.2(11) unless
they are primitive functions. This rule exists so that the default makes sense
in inherited primitives. But we really would have preferred that the default
expressions weren't inherited; they only make sense on the base routines. That
is a problem that probably isn't worth solving though.

Of course, now that we had functions, we had to implement them. The first try
was:

   function Classes_Root return Key_Type is
   begin
       return (Ada.Finalization.Limited_Controlled with
           Handle => 16#80000000#, -- Windows magic number
           Predefined_Key => True, ...);
   end Classes_Root;

But our friendly compiler told us that THIS was illegal, because this is
return-by-reference type, and the aggregate doesn't have the required
accessibility.

So we had to add a library package-level constant and return that:

   Standard_Classes_Root : constant Key_Type := (Ada.Finalization.Limited_Controlled with
       Handle => 16#80000000#, -- Windows magic number
       Predefined_Key => True, ...);

   function Classes_Root return Key_Type is
   begin
       return Standard_Classes_Root;
   end Classes_Root;

But of course THAT is illegal (its the original problem all over again), so we
had to turn that into a variable and initialize it component-by-component in
the package body's elaboration code:

   Standard_Classes_Root : Key_Type;

   function Classes_Root return Key_Type is
   begin
       return Standard_Classes_Root;
   end Classes_Root;

begin
   Standard_Classes_Root.Handle := 16#80000000#; -- Windows magic number
   Standard_Classes_Root.Predefined_Key => True;
   ...

Which is essentially how it is today.

This turned into such a mess that we gave up deriving from it altogether, and
created an entirely new higher-level abstraction to provide the most commonly
used operations in an easy to use form. Thus, we ended up losing out on the
benefits of O-O programming here.

I certainly hope that newcomers to Ada don't run into a problem like this,
because it is a classic "stupid language" problem.

Simply having a way to initialize a limited constant with an aggregate would be
sufficient to fix this problem. "Constructor functions" might add
orthogonality, but seem unnecessary to solve the problem of being able to have
constants as part of an abstraction's specification.

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

From: Robert Duff
Sent: Friday, February 8, 2002, 10:12 AM

> Simply having a way to initialize a limited constant with an aggregate would
> be sufficient to fix this problem. "Constructor functions" might add
> orthogonality, but seem unnecessary to solve the problem of being able to
> have constants as part of an abstraction's specification.

Surely you don't mean that we would allow limited aggregates only for
initializing stand-alone constants?!  Surely, you could use them to
initialize variables. And if they can be used to initialize variables,
surely initialized allocators should be allowed. And of course,
parameters.

In *my* programs, much of the data is heap-allocated. I want to say:

    X: Some_Ptr := new T'(...);

when T is limited. Allowing only constants would solve about 1% of *my*
problem.

Are you saying that this is illegal:

    P(T'(...));

and I have to instead write:

    Temp: constant T := (...);

    P(Temp);

?!  That sort of arbitrary restriction is what makes people laugh at the
language.

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

From: Tucker Taft
Sent: Friday, February 8, 2002,  5:48 PM

Given the relative complexity of the constructor function
concept compared to the other de-limiting ideas, I would propose
we split the AI. The simpler one would allow:

   1) Aggregates of limited objects, with use of a subtype_mark to mean
      default init of a component;
   2) Explicit initialization of limited objects, both in a declaration and
      an allocator, from an aggregate, with the aggregate built "in place" in
      the target.

The more complex one would address functions constructing limited objects
on behalf of the caller.

The aggregate one seems very straightforward. Almost just eliminate
the existing restriction, presuming that compilers have already learned
how to build aggregates "in place" for controlled types.

The function one looks like a lot of work.

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

From: Robert I. Eachus
Sent: Monday, October 7, 2002  9:55 AM

It certainly doesn't have my support.  When I see limited types, I think
in terms of objects that have components of a task type.  Not required,
but always possible.  When you have a limited view of a type, you don't
know whether it has a component of a task type.  What does it mean to
have an initialization for such an object?  Do you create a new task
object?  If so who is the master?  If not, what happens to the
"wonderful" idea of full coverage for limited types?

In my opinion, since an aggregate or initialization of a record in
general does guarantee that all fields of an object are initialized,
what guarantee are we extending to limited views?  It seems to me that
all that would be accomplished is to add a false feeling of security.
 Oh, and notice that the discriminants of an object of a limited type
will always be initialized, that is not at issue here.

As far as I am concerned, this AI is an attempt to fix something that is
not broken.  The only objects that must be limited are those which  are
or contain tasks.  Most other limited objects are limited either because
the designer of the type wants to do deep copying or some other
non-trivial semantics which would be bypassed by this scheme, or are
generic formal types where the designer of the generic sees no need for
assignment semantics for the actual type.  The first would be broken by
this scheme, and allowing initialization of generic formal limited types
would only lead to mischief:

        generic
            type L is limited private;
        procedure Mischief (LP: in out L);

        procedure Mischief(LP: in out L) is
            Oops: L := LP;
        ...

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

From: Tucker Taft
Sent: Monday, October 7, 2002  10:35 AM

     I think you may have missed the ability to "default initialize"
a component by using the subtype name instead of an expression.
For a component that is a task type, protected type, or limited
private type, this is your only alternative.  So full coverage
is still guaranteed, but for these components, you must explicitly
request default initialization ;-).

Note that for tagged types, the other reason for being limited is
to allow for limited extensions.  In general, I think there are plenty
of situations where copying is not meaningful, but initialization-by-
aggregate would be helpful and more maintainable.

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

From: Robert Dewar
Sent: Monday, October 7, 2002  10:43 AM

Can we get some idea of where the request for limited aggregates is coming
from. I have certainly never seen any requests from our users in this area
or questions for which this would be the answer.

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

From: Robert Eachus
Sent: Monday, October 7, 2002  6:37 PM

Tucker Taft wrote:

>     I think you may have missed the ability to "default initialize"
> a component by using the subtype name instead of an expression.
> For a component that is a task type, protected type, or limited
> private type, this is your only alternative.  So full coverage
> is still guaranteed, but for these components, you must explicitly
> request default initialization ;-).

I didn't overlook this, just didn't see the point of calling something
full coverage when it isn't.  All you can count on for records and
aggregates is that all discriminants have a value, and all access
components have a (potentially null) value.  As for task objects, you
can't count on a value at all times, since the task component may be
accessable before it is elaborated, or it may become terminated.

> Note that for tagged types, the other reason for being limited is
> to allow for limited extensions.  In general, I think there are plenty
> of situations where copying is not meaningful, but initialization-by-
> aggregate would be helpful and more maintainable.

I'll have to respectfully agree to disagree.  In my programming style, I
almost always expect a limited type to contain state, either explicitly
as a protected object or task object, or implicitly through operations
hidden from the view of the user of the type.  Allowing a user to
override the hidden state seems to me to always be a bad thing, and I am
having trouble understanding where you think this new freedom would be
useful.

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

From: Robert Duff
Sent: Monday, October 7, 2002  5:30 PM

> It certainly doesn't have my support.  When I see limited types, I think
> in terms of objects that have components of a task type.  Not required,
> but always possible.

There are many reasons to use limited types other than tasks: protected
objects, access discriminants, self-relative pointers, and any other
type where copying does not make sense.  As I said in the AI, "after
all, the whole point of a compile-time type system is to allow
programmers to formally express which operations do and do not make
sense for each type."

>...  When you have a limited view of a type, you don't
> know whether it has a component of a task type.  What does it mean to
> have an initialization for such an object?  Do you create a new task
> object?  If so who is the master?  If not, what happens to the
> "wonderful" idea of full coverage for limited types?

I think you're missing a couple of points: First, to write an aggregate,
it has to be a record (or array).  We're not proposing to allow
aggregates of a task type.  Second, we're not proposing to allow
initialization via anything other than an aggregate.  So, for example,
you can't say "Oops: L := LP;", because LP is not an aggregate.

Perhaps an example would help:

    package P1 is
        type T1 is limited private;
    private
        type T1 is task...
    end P1;

    use P1;

    package P2 is
        type T2 is limited private;
	...
    private
        type T2 is limited
            record
                X: T1;
                Y: Integer;
                Z: Boolean;
            end record;
    end P2;

    package body P2 is

        procedure P is
            T2_Obj: T2 := (T1, 123, False); -- currently illegal
        begin
            ...

The AI proposes that the above should be legal.  As you say, we don't
know (in the body of P2) whether T1 contains tasks.  In this case it
does.  The semantics of the declaration of T2_Obj are to
default-initialize T2_Obj.X, set T2_Obj.Y to 123, and set T2_Obj.Z to
False.

To answer your above questions about tasks: yes, the initialization of
T2_Obj.X creates a task, just as if you had said "X: T1;" as a
standalone variable declaration.  And the master of this task is that of
T2_Obj, namely procedure P.

I think the AI says all this, but perhaps not very clearly.  Maybe I
should add an example like the above.

And note that we're taking full advantage of the full coverage rules,
above.  All three components of the aggregate are *required* -- either
as an expression (giving the value), or as a type_mark (requesting
default initialization).  There's no way to *accidentally* leave out one
of the components.

Note also that since T1 is limited, and not a record, you would not be
allowed to give an expression (such as the name of some object of type
T1) for component X.

> In my opinion, since an aggregate or initialization of a record in
> general does guarantee that all fields of an object are initialized,
> what guarantee are we extending to limited views?

We're extending the same "full coverage" benefits to limited records.
This benefit is currently missing from the language.  We're not taking
away any assurances.

>...  It seems to me that
> all that would be accomplished is to add a false feeling of security.

No, there's no "false feeling of security" -- just as in the existing
language, aggregates must be complete.

>  Oh, and notice that the discriminants of an object of a limited type
> will always be initialized, that is not at issue here.

True.  Discriminants are always initialized, and this AI does not change
that fact.

> As far as I am concerned, this AI is an attempt to fix something that is
> not broken.  The only objects that must be limited are those which  are
> or contain tasks.

As I said above, there are many other uses for limited types in Ada 95,
some of which require limitedness by the language rules, and some of
which require limitedness to correctly define the appropriate behavior
of the type.

>...Most other limited objects are limited either because
> the designer of the type wants to do deep copying or some other
> non-trivial semantics which would be bypassed by this scheme, or are
> generic formal types where the designer of the generic sees no need for
> assignment semantics for the actual type.  The first would be broken by
> this scheme,

No.  Such types are private, not record, and thus would not allow
initialization.

>... and allowing initialization of generic formal limited types
> would only lead to mischief:
>
>         generic
>             type L is limited private;
>         procedure Mischief (LP: in out L);
>
>         procedure Mischief(LP: in out L) is
>             Oops: L := LP;
>         ...

No, this "mischief" would still be illegal -- LP is not an aggregate,
and in fact no aggregate would be legal there, because L is not a
record (as far as the generic knows).

In the above example, if you add another component to T2, you can't
forget to initialize that component of T2_Obj, because the compiler will
tell you.  I had this very bug just last week.  I found it by reading my
own code before I ever executed it, so no real harm was done, but it
would have been better if the compiler could catch it.  The compiler
couldn't, because I was forced to write a series of assignment
statements, one for each component, instead of an aggregate.

I really think the language change proposed by this AI is *very* small.
It doesn't introduce all kinds of tasking issues or anything like that,
nor does it introduce any new run-time concepts for the compiler to deal
with.  It just says you can get the benefit of full coverage rules when
initializing limited records.  Small change, large benefit.

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

From: Robert Eachus
Sent: Monday, October 7, 2002  7:17 PM

Robert A Duff wrote:

>Perhaps an example would help:
>
That example helps a lot.  The INTENT is that you can use aggregates to
initialize limited objects that may have limited private components.
 For the limited private, task, or protected object fields, the only
allowed value in the aggregate is the subtype name.

>We're extending the same "full coverage" benefits to limited records.
>This benefit is currently missing from the language.  We're not taking
>away any assurances.
>
>
This is where the devil is in the details, and the whole proposal is
necessary to see the details.  The problem that I am seeing is that
allowing an aggregate as an initial value of a limited object, allows
for an object  to be the value given for a limited component.  To use
Bob Duff's example:

    package P1 is
        type T1 is limited private;
    private
        type T1 is task...
    end P1;

    use P1;

    package P2 is
        type T2 is limited private;
	...
    private
        type T2 is limited
            record
                X: T1;
                Y: Integer;
                Z: Boolean;
            end record;
    end P2;

    package body P2 is

        procedure P is
            T1_Obj: T1; --legal;
            T2_Obj: T2 := (T1_Obj, 123, False); -- Oops!
        begin
            ...

>Note also that since T1 is limited, and not a record, you would not be
>allowed to give an expression (such as the name of some object of type
>T1) for component X.
>
This is the rule that I am unable to deduce from the (draft) AI. Doesn't
mean I think the AI is necessary and useful, but with this rule added it
at least makes sense.  Hmm. Thinking about it, even if the full
declaration of T1 was visible, there is still a potential problem.  The
rule has to be that limited components must be represented in the
aggregate by a subtype mark.  (And thus every object of type T2 in Bob's
example gets a new value of type T1.)  If T1 is visibly not limited in
the body of P2, of course, this AI does not apply, and an aggregate
initialization (or assignment) for objects of T2 is allowed.

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

From: Robert Duff
Sent: Monday, October 7, 2002  7:45 PM

> That example helps a lot.

Good.  I should put something like it in the AI.

>...  The INTENT is that you can use aggregates to
> initialize limited objects that may have limited private components.
>  For the limited private, task, or protected object fields, the only
> allowed value in the aggregate is the subtype name.

Right.

> >We're extending the same "full coverage" benefits to limited records.
> >This benefit is currently missing from the language.  We're not taking
> >away any assurances.
> >
> >
> This is where the devil is in the details, and the whole proposal is
> necessary to see the details.  The problem that I am seeing is that
> allowing an aggregate as an initial value of a limited object, allows
> for an object  to be the value given for a limited component.  To use
> Bob Duff's example:
>
>     package P1 is
>         type T1 is limited private;
>     private
>         type T1 is task...
>     end P1;
>
>     use P1;
>
>     package P2 is
>         type T2 is limited private;
> 	...
>     private
>         type T2 is limited
>             record
>                 X: T1;
>                 Y: Integer;
>                 Z: Boolean;
>             end record;
>     end P2;
>
>     package body P2 is
>
>         procedure P is
>             T1_Obj: T1; --legal;
>             T2_Obj: T2 := (T1_Obj, 123, False); -- Oops!

This is forbidden by the following rule in the AI:

| Replace the above deleted rules with a single Legality Rule just before
| 7.5(2):
|
| For an assignment operation that initializes an object with the value of
| a limited expression, the expression shall be one of the following:
|
|     - an aggregate
|     - a qualified_expression whose expression is one of these
|     - a parenthesized_expression whose expression is one of these


T1_Obj is not one of the above (in less formal terms: an aggregate
wrapped in zero or more qual_exps or parens), so it cannot be used to
initialize the component in the aggregate.
The AARM annotation in the AI gives this example as one of the illegal
things:

|     Ramification: These are the forbidden assignment operations,
|     where X is the name of a limited object:
...
|         - (Component => X)
|            -- the expression of a component_association of an aggregate

The point of the rule is that you cannot extra the value out of a
limited object (like T1_Obj), and use it to initialize another object
(like T2_Obj.X) -- that would violate the whole point of limited types,
which is to prevent copying.

In the above example, the only possibility for component X in the
aggregate is a subtype_mark.  It can't be an object name (by the above
rule), and it can't be an aggregate (because T1 is not a record).

Yes, the devil lurks in the details as usual, but I believe I've caught
all of these cases.

>         begin
>             ...
>
> >Note also that since T1 is limited, and not a record, you would not be
> >allowed to give an expression (such as the name of some object of type
> >T1) for component X.
> >
> This is the rule that I am unable to deduce from the (draft) AI.

It's the rule I quoted above.

>... Doesn't
> mean I think the AI is necessary and useful, but with this rule added it
> at least makes sense.  Hmm. Thinking about it, even if the full
> declaration of T1 was visible, there is still a potential problem.  The
> rule has to be that limited components must be represented in the
> aggregate by a subtype mark.

Yes, since T1 is not a record, it must be represented by a subtype_mark
in any aggregate of type T1.

>...(And thus every object of type T2 in Bob's
> example gets a new value of type T1.)

Right -- just as if you declared several standalone variables of type
T1.

>...  If T1 is visibly not limited in
> the body of P2, of course, this AI does not apply, and an aggregate
> initialization (or assignment) for objects of T2 is allowed.

Yes.

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

From: Robert Eachus
Sent: Monday, October 7, 2002  10:50 PM

Let me change the example again to show you the one case I am still
"worried about:"

package P1 is
        type T1 is limited private;
         ...
private
       task type T3 is...
       type T1 is limited
          record
              A:  Boolean := False;
              B:  T3;
          end record;
end P1;

package P1.P2 is
    type T2 is limited private;
private
       type T2 is limited
           record
               X: T1;
               Y: Integer;
               Z: Boolean;
          end record;
 end P1.P2;

package body P1.P2  is
       T1_Obj: T1 := (True, T3) -- legal?
       T2_Obj: T2 := ( (True, T3), 123, False); -- legal?
       ...
end P1.P2;

I used child packages to show the methodology issue to its greatest
extent, and yes the legality questions are really questions of
methodology.  I happen to like the idea that it is illegal currently for
a child unit to "forge" an object of type T2, but others may feel
differently.  (Yes, you can muck up an existing object if the full type
is in the parent package body.  I tend to use the old Tucker Taft
ammendment to avoid that.)  However, I definitely feel that the second
case is more egregious than the first.

And this is where the question of style comes to the fore.  My style is
to, where appropriate, hide limitedness by use of  access types.
 Changing the state space of a limited object by adding new state values
is not something to be done on the fly.  You should create a new
"wrapper" type with the new state varibles and leave the original
abstraction untouched.  This allows the base abstraction to be tested
thoroughly, and any wrapper types to be tested as such without worrying
about the internal black box of the base type.

With non-limited types you always have the (potential) worry of user
assignment and user initialization.  I'm not really concerned about the
cases when someone goes outside of Ada by using pragma Interface or
whatever.  Just that most non-limited types can't have dependable state:

     A, B: Foo; -- not a problem case, just for economy of expression....
     C: Foo := A;
   begin
      B := C;
      ...

Yes, if  Foo is a controlled type you can make the state work, if you
are clever.  But I usually find it easier to make Foo a controlled type
with an access value pointing to the real (and limited) state.  Your
milage may vary.

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

From: Robert Duff
Sent: Tuesday, October 8, 2002  7:39 AM

>        T1_Obj: T1 := (True, T3) -- legal?
>        T2_Obj: T2 := ( (True, T3), 123, False); -- legal?

Yes, both legal.

These are essentially equivalent to:

        T1_Obj: T1; -- T1_Obj.B gets default initialized.
	T1_Obj.A := True;
        T2_Obj: T2; -- T2_Obj.X.B gets default initialized.
        T2_Obj.X.A := True;
        T2_Obj.Y := 123;
        T2_Obj.Z := False;

except that the version with the aggregates is less error-prone and more
maintainable.

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

From: Tucker Taft
Sent: Tuesday, October 8, 2002  4:41 AM

> package body P1.P2  is
>        T1_Obj: T1 := (True, T3) -- legal?

Yes, the new AI would allow this.

>        T2_Obj: T2 := ( (True, T3), 123, False); -- legal?

And this.

>        ...

Without the new AI, you could do essentially the
same thing, but you would not have any full coverage help,
and you would have to separate the declaration from
the initialization, which could create access-before-
full-initialization problems.

E.g.:

    T1_Obj : T1;
    T2_Obj : T2;
 ...
begin
    T1.A := True;
    T2.X.A := True;
    T2.Y := 123;
    T2.Z := False;
 ...
> end P1.P2;
>
> I used child packages to show the methodology issue to its greatest
> extent, and yes the legality questions are really questions of
> methodology.  I happen to like the idea that it is illegal currently for
> a child unit to "forge" an object of type T2, but others may feel
> differently.  (Yes, you can muck up an existing object if the full type
> is in the parent package body.  I tend to use the old Tucker Taft
> ammendment to avoid that.)  However, I definitely feel that the second
> case is more egregious than the first.

I have lost you here, since you can only write an
aggregate if the type is not private at the point
of the aggregate.  If the type is not private, then
what sort of protection can you have?  At least with
the aggregate all the initialization happens very
visibly at the point of declaration.

This whole argument seems a bit murky to me...
Can you show specifically what protection
you are losing?  For me, this is only adding
safety and maintainability, and not removing
anything.

In answer to the question of need, it has almost
reached the level of "conventional wisdom" that
limited types are a pain, and one limited component
"poisons" the whole record.  With Ada 95, there are
more places where you need to make types limited
simply to allow greater extensibility or take
advantage of protected types, with the
net effect that there are more places where this
"poison" is painful.  Having already established
that aggregates used in initializers need to be
built "in place" for controlled types, the model
of aggregates has already shifted enough to easily
accommodate this proposed lessening of the pain
associated with limited components.

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

From: Robert Eachus
Sent: Wednesday, October 9, 2002  1:08 AM

Sometimes it takes some back-and-forth on an ARG issue before it is
clear why there are two different points of view.  Here we have it in a
nutshell.  One man's poison is another's plum pudding.  In this case, we
have gotten through the issues of writeup to make it clear what is
intended and what is not.  That is good, great, and wonderful.

Now we can discuss style and methodology.  My style is always to ask
myself, not must this type be limited, but can I make this type limited.
 In security doctrine this is called the principle of least privilege.
 The idea is that there is a least amount of priviledge that a person,
program, etc., needs to do its job.  If you need read access to a some
file, I should give you read access not read write, and so on.

A good example is a queue.  It is in the nature of queues that copying
them (and for that matter, testing for equality) is an invalid
operation.  So queues should be limited objects whether the
implementation includes a task or protected object directly in the queue
or not.

Stacks are different.  There are many cases where saving and restoring
the state of a stack is a meaningful operation, but for other stacks
copying/assignment may be meaningless.  So provide two different
implementations of stacks, one limited, the other not.  The limited
stack may actually be implemented by using the non-limited version, but
it is worth having both to allow the software architect to make a
statement about reality.  This may seem to multiply container entities
beyond reason, but it practice it doesn't.  Tasking safe (protected)
data structures should never be copied--except inside the implementation
to grow them or compact storage.  The stacks you want to be able to copy
are only used by a single thread, and so on.

So I am glad to help make the AI more comprehensible--but I still don't
find it on a list of missing features I want.

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

From: Robert A. Eachus
Sent: Wednesday, October 9, 2002  1:20 AM

Robert A Duff wrote:

>Yes, both legal.
>
>
Well, the AI would make both legal.

>These are essentially equivalent to...
>
>...except that the version with the aggregates is less error-prone and more
>maintainable.
>
Sigh.  I guess my point was a bit too subtle.  As far as I am concerned
both have the same bug in that they create an invalid value for a
(nested) object of type T1.  If a type is limited, it is always in my
mind error-prone and less maintainable not to use the actual
constructor(s) and operations for the type.  But as I said in my reply
to Tuck, my only real argument against the AI at this point is that I
see no need for it.  If others see it as very useful in their style of
programming, so be it.  But from my point of view, anything on my wish
list has higher priority. ;-)

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

From: Robert Dewar
Sent: Wednesday, October 9, 2002  6:20 AM

<<A good example is a queue.  It is in the nature of queues that copying
them (and for that matter, testing for equality) is an invalid
operation.  So queues should be limited objects whether the
implementation includes a task or protected object directly in the queue
or not.>>

Sorry I do not understand this at all. Why is it in the nature of a queue
that copying them is invalid?

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

From: Robert Duff
Sent: Wednesday, October 9, 2002  9:09 AM

>   If a type is limited, it is always in my
> mind error-prone and less maintainable not to use the actual
> constructor(s) and operations for the type.

Yes, but my point is that writing those constructors is much *less*
error-prone if they can use aggregates.

I said in a previous message that I had a bug a week or so ago that
would have been prevented by using aggregates.  (In fact, it was found
by reading the code, but I might have missed it, and it might have been
a run-time bug.)  This bug was *in* a very straightforward constructor.
This particular package and its children have about 50 such
constructors.  These would all be safer and more maintainable if they
could use aggregates.

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

From: Robert Dewar
Sent: Wednesday, October 9, 2002  9:22 AM

<<Yes, but my point is that writing those constructors is much *less*
error-prone if they can use aggregates.>>

I strongly agree with this, the use of aggregates systematically to
make sure that the addition of new components gets properly handled
at all constructor points is valuable.

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

From: Randy Brukardt
Sent: Wednesday, October 9, 2002  8:55 PM

Robert Eachus wrote:

> Allowing a user to override the hidden state seems to me to always be
> a bad thing, and I am having trouble understanding where you think this
> new freedom would be useful.

This isn't for the user so much as for the constructor of an abstraction.
The big benefit is that it would allow deferred constants of limited types.
We ran into this problem in Claw, and I sent a long explanation of it during
the original discussion of this AI back in February. You must have missed
it, so here it is again:

Here is an example that came up in Claw where we really wanted constants of
a limited type:

(* Editor's note: See the message above from February 7, 2002 *)

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


Questions? Ask the ACAA Technical Agent