!standard 3.3.1 (05) 03-02-19 AI95-00287/04 !standard 3.6.2 (16) !standard 3.8 (08) !standard 3.8 (25) !standard 4.3 (03) !standard 4.3.1 (04) !standard 4.3.1 (08) !standard 4.3.1 (16) !standard 4.3.1 (17) !standard 4.3.1 (20) !standard 4.3.2 (04) !standard 4.3.3 (03) !standard 4.3.3 (05) !standard 4.3.3 (07) !standard 4.3.3 (24) !standard 4.8 (05) !standard 7.5 (01) !standard 7.5 (02) !standard 7.5 (08) !standard 7.5 (09) !standard 7.5 (10) !standard 7.5 (11) !standard 7.5 (12) !standard 7.5 (13) !standard 7.5 (14) !standard 7.5 (15) !standard 9.1 (21) !standard 9.4 (23) !standard 12.4 (08) !class amendment 02-02-06 !status Amendment 200Y 03-02-19 !status ARG Approved 5-0-3 03-02-08 !status work item 02-10-09 !status received 02-02-06 !priority Medium !difficulty Medium !subject Limited Aggregates Allowed !summary Limited aggregates are allowed, and limited objects may be initialized using aggregates. This implies that it is possible to declare constants (including deferred constants) of a limited type. A box ("<>") is allowed in place of an expression to for a component value in an aggregate. Box means to use default initialization for that component. !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. However, 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. These two features ought to be orthogonal, allowing programmers to get the benefits of both. 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. However, this benefit is unavailable for limited types. Moreover, the lack of this capability makes it impossible to declare constant limited objects. !proposal The primary goal of this proposal is to allow aggregates to be of a limited type, and to allow such aggregates as the explicit initial value of objects (primarily objects declared by object declarations, and objects created by allocators). We do not compromise the safety of limited types. In particular: - Assignment statements are still illegal for limited types. - The initialization expression for a limited object cannot be the name of another object, as that would constitute copying out of that object. - Aggregates are still allowed only for records and arrays (and extension aggregates for record extensions). For example, there is no such thing as an aggregate of a task type, or of a limited private type. We also propose to allow a box ("<>") in place of an expression for a component of an aggregate. The box is an explicit request to use default initialization for that component. The box capability is needed in the case of limited aggregates, because some components might be limited and still not allow aggregates. For example, if a limited record has a task component, that component needs to be specified as a box, which means to create a new task. The box capability is also useful for all flavors of private types, where an aggregate cannot be written and an explicit initializing value may not be available. We allow the box capability in general (even when the component is nonlimited), because an arbitrary restriction would add complexity, and because such a restriction would cause trouble with the generic contract model and the package-private contract model. We retain the benefit of full-coverage checking for aggregates, for both limited and nonlimited types. It is possible for some component of the aggregate to be undefined, but only upon explicit request of the programmer (via the box notation). The intent of the full-coverage checking is to prevent some components to be left out by accident, and this intent is retained. (Note that even without this proposal, one could write an expression for a component where the expression is the name of an uninitialized integer variable. The point of full coverage is simply to ensure that the programmer does not forget about some of the components -- including the ones invented tomorrow.) From the programmer's point of view, initializing a limited object with an aggregate is essentially equivalent to what one would write without this AI: namely, declare an object, and fill in all the components other than the <> ones. For example: X: T := (A => A_Val, B => V_Val, C => <>); is essentially equivalent to: X: T; ... X.A := A_Val; X.B := B_Val; -- Use default for X.C. except that the comment in the latter case is compile-time-checkable code in the former case. In both cases, if C has a default expression declared in the record type, that is used, and if not, the normal default initialization for the type of C is used. Similarly: X: T := (A => A_Val, B => V_Val, others => <>); is essentially equivalent to: X: T; ... X.A := A_Val; X.B := B_Val; -- Use defaults for the other components. We say "essentially" equivalent, because there is one run-time difference: If the defaults for A and B have side effects, they happen in the latter case, but not in the former case. But programmers can pretty much ignore that fact, because defaults shouldn't normally have important side effects. !wording Delete "nonlimited" from 4.3(3), 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 a limited object with the value of an expression, the expression shall be a (possibly parenthesized or qualified) aggregate. AARM annotation: Ramification: These are the forbidden initializing 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. (In fact, as pointed out by the "To be honest" in AARM-7.5(1.c), 7.5(1) is actually wrong.) 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 box in place of the expression, but only if named notation is used: 4.3.1(4): record_component_association ::= [ component_choice_list => ] expression | component_choice_list => <> 4.3.1(16) says: If a record_component_association has two or more associated components, all of them shall be of the same type. This should not apply when a box is used, so add "with an expression": If a record_component_association with an expression has two or more associated components, all of them shall be of the same type. After 4.3.1(17), add: A record_component_association for a discriminant without a default_expression shall have an expression rather than a box. 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 box, if the component_declaration has a default_expression, that default_expression defines the value for the the associated component(s); otherwise, the associated component(s) are initialized by default as for a standalone object of the component subtype (see 3.3.1). ---------------- 4.3.3(2-6) says: Syntax array_aggregate ::= positional_array_aggregate | named_array_aggregate positional_array_aggregate ::= (expression, expression {, expression}) | (expression {, expression}, others => expression) named_array_aggregate ::= (array_component_association {, array_component_association}) array_component_association ::= discrete_choice_list => expression Change these syntax rules to allow a box in place of an expression, but only in named notation. Syntax array_aggregate ::= positional_array_aggregate | named_array_aggregate positional_array_aggregate ::= (expression, expression {, expression}) | (expression {, expression}, others => expression) | (expression {, expression}, others => <>) named_array_aggregate ::= (array_component_association {, array_component_association}) array_component_association ::= discrete_choice_list => expression discrete_choice_list => <> After 4.3.3(23), add: Each array component expression defines the value for the associated component(s). For an component given by a box, 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: AI-318 generalizes this idea to allowing constructor functions in addition to aggregates. AI-318 involves a substantial implementation effort, whereas this AI is almost trivial to implement; that's the reason for the split. A purist would argue that it seems ugly to allow limited aggregates (this AI), but not allow them to be wrapped in a constructor function (AI-318). However, from a practical point of view, allowing limited aggregates covers most of the cases, because most of the objects in question are heap objects. Therefore, the constructor function can return access-to-limited; the benefit is that that function can use an aggregate to initialize the heap-allocated object. See below for an example (function New_T). 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. This capability is particularly important for deferred constants. 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 a box used in a component association is essentially the same as for a subtype_mark used as the ancestor_part of an extension_aggregate. Limited types offer other advantages in addition to lack of copying: access discriminants, the ability to take 'Access of the "current instance", and the ability to extend the type with task or protected components. 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; -- Illegal! will still be forbidden by the rule added just before 7.5(2). Similarly, if a limited type has a task component, an aggregate of the outer type cannot use the name of some task object for the task component. A new task must be created, just as if a normal object of the outer type were declared without explicit initialization. Note that when a component of an aggregate is given by a box, 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 tasking or finalization occur. The box notation is allowed only with named notation, because positional notation might be error prone. For example, in "(X, <>, Y, <>)", there is no type associated with the boxes, so you might not be leaving out the components you thought you were leaving out. The "others => <>" notation is allowed even when the associated components are not of the same type. The "others => expression" notation, however, still retains the Ada 83 requirement that all the associated components be of the same type. This rule was presumably invented because it would be weird to resolve the expression more than once with different expected types each time. But there is no particular type associated with the box, so "others => <>" is harmless. Furthermore, "others => <>" is actually *safer* than "others => expression", because the former uses the default values declared in the record itself. Similarly, "A | B | C => expression" requires A, B, and C to have the same type, whereas "A | B | C => <>" does not. There is one slight oddity, which I believe was pointed out by Dan Eilers. This AI allows the creation of constants whose value can be modified: type Self_Ref is limited private; type Self_Ref_Ptr is access all Self_Ref; type Self_Ref is limited record Comp: Integer; Self: Self_Ref_Ptr := Self_Ref'Unchecked_Access; end record; Const: constant Self_Ref := (Comp => 123, others => <>); Const.Self.all.Comp := 456; -- Modify component of constant object. We choose not to do anything to prevent this oddity. It's not much odder than the fact that Finalize can modify constant objects. This is one more case where the compiler had better not store the constant object in ROM. If programmers are using such self-referential types, they can easily control (prevent) this kind of thing by making the type private. 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. Rejected Alternatives: We considered using a different notation instead of the box. In particular, one could use a subtype_mark, with similar semantics to the box. This idea came from the subtype_mark that is allowed as the ancestor_part in an extension_aggregate. This idea was rejected because: - It requires extra rules. We have to say that the type of that subtype_mark is the right type. And we have to ensure that the constraints on that subtype are appropriate. - Some implementers find that allowing subtype_marks and expressions in the same context complicates name resolution, in addition to the complication of implementing the rules in the previous bullet. - The analogy with extension_aggregates is weak. For an extension_aggregate, the subtype_mark carries information about which ancestor type to use, whereas all we want here is a single bit of information: "use the default". - Some people found "subtype_mark" to be an odd way to specify "use the default expression for the component" (as opposed to using the default initialization for the component's type). But we certainly don't want to allow bypassing of the default expression for the component, and we certainly don't want two different notations. !example Here is an example of an abstraction where copying makes no sense, so the type T should be limited. In this case, T has a component of type Semaphore, which is protected, so T *must* be limited. We have a constructor function New_T, which creates a new object of type T in the heap, and returns a pointer to it. package P is type T is limited private; type T_Ptr is access T; function New_T(X: Integer; Y: Boolean) return T_Ptr; ... private protected type Semaphore is ...; type T is limited record Sem: Semaphore; X: Integer; Y: Boolean; end record; end P; package body P is function New_T(X: Integer; Y: Boolean) return T_Ptr is begin return new T'(Sem => <>, X => X, Y => Y); -- This AI makes this legal. end New_T; ... end P; The point of this proposal is that if you add a new component to type T, you can't forget to modify the constructor accordingly, because the compiler will remind you. Without this proposal, New_T would have to look like this: function New_T(X: Integer; Y: Boolean) return T_Ptr is Result: T_Ptr := new T; begin Result.X := X; Result.Y := Y; return Result; end New_T; Here, Result.Sem is silently default-initialized, and the other components are initialized using assignment statements. The problem is that if a new component is added to T, New_T now has a bug. Imagine that T is actually derived from some other type, and a new component is added to that other type, so T silently inherits a new component. That would make the bug even more likely, because the constructor New_T is far away from the original type in the source code, and there may be many types in the hierarchy, making it difficult to track down all the places in the source code that need to be changed. This proposal solves that problem with a compile-time rule. Consider also a limited object declared in a package body. Without this proposal, the non-limited components of that object need to be initialized by assignment statements. But in this case, those statements are typically far separated from the declaration of the object itself. For a non-limited object, one would initialize the object at its declaration point, for the usual reasons. This proposal allows the same benefit to apply to the limited object. !corrigendum 3.3.1(5) @drepl An @fa without the reserved word @b declares a variable object. If it has a @fa or an @fa that defines an indefinite subtype, then there shall be an initialization expression. An initialization expression shall not be given if the object is of a limited type. @dby An @fa without the reserved word @b declares a variable object. If it has a @fa or an @fa that defines an indefinite subtype, then there shall be an initialization expression. !corrigendum 3.6.2(16) @drepl @xindent<@s9<48 A component of an array can be named with an @fa. A value of an array type can be specified with an @fa, unless the array type is limited. For a one-dimensional array type, a slice of the array can be named; also, string literals are defined if the component type is a character type.>> @dby @xindent<@s9<48 A component of an array can be named with an @fa. A value of an array type can be specified with an @fa. For a one-dimensional array type, a slice of the array can be named; also, string literals are defined if the component type is a character type.>> !corrigendum 3.8(8) @ddel A @fa is not permitted if the component is of a limited type. !corrigendum 3.8(25) @drepl @xindent<@s9<61 A component of a record can be named with a @fa. A value of a record can be specified with a @fa, unless the record type is limited.>> @dby @xindent<@s9<61 A component of a record can be named with a @fa. A value of a record can be specified with a @fa.>> !corrigendum 4.3(3) @drepl The expected type for an @fa shall be a single nonlimited array type, record type, or record extension. @dby The expected type for an @fa shall be a single array type, record type, or record extension. !corrigendum 4.3.1(4) @drepl @xcode<@fa ] expression>> @dby @xcode<@fa ] expression component_choice_list =@> <@>>> !corrigendum 4.3.1(8) @drepl The expected type for a @fa shall be a single nonlimited record type or record extension. @dby The expected type for a @fa shall be a single record type or record extension. !corrigendum 4.3.1(16) @drepl Each @fa shall have at least one associated component, and each needed component shall be associated with exactly one @fa. If a @fa has two or more associated components, all of them shall be of the same type. @dby Each @fa shall have at least one associated component, and each needed component shall be associated with exactly one @fa. If a @fa with an expression has two or more associated components, all of them shall be of the same type. !corrigendum 4.3.1(17) @dinsa If the components of a @fa are needed, then the value of a discriminant that governs the @fa shall be given by a static expression. @dinst A @fa for a discriminant without a @fa shall have an @fa rather than <@>. !corrigendum 4.3.1(20) @dinsb The expression of a @fa is evaluated (and converted) once for each associated component. @dinst For a @fa with an @fa, the @fa defines the value for the associated component(s). For a @fa with a <@>, if the @fa has a @fa, that @fa defines the value for the the associated component(s); otherwise, the associated component(s) are initialized by default as for a standalone object of the component subtype (see 3.3.1). !corrigendum 4.3.2(4) @drepl The expected type for an @fa shall be a single nonlimited type that is a record extension. If the @fa is an @fa, it is expected to be of any nonlimited tagged type. @dby The expected type for an @fa shall be a single type that is a record extension. If the @fa is an @fa, it is expected to be of any tagged type. !corrigendum 4.3.3(3) @drepl @xcode<@fa@ft<@b>@fa< =@> expression)>> @dby @xcode<@fa@ft<@b>@fa< =@> expression) | (expression {, expression}, >@ft<@b>@fa< =@> <@>)>> !corrigendum 4.3.3(5) @drepl @xcode<@fa expression>> @dby @xcode<@fa expression | discrete_choice_list =@> <@>>> !corrigendum 4.3.3(7) @drepl The expected type for an @fa (that is not a subaggregate) shall be a single nonlimited array type. The component type of this array type is the expected type for each array component expression of the @fa. @dby The expected type for an @fa (that is not a subaggregate) shall be a single array type. The component type of this array type is the expected type for each array component expression of the @fa. !corrigendum 4.3.3(24) @dinsb The bounds of the index range of an @fa (including a subaggregate) are determined as follows: @dinst Each array component expression defines the value for the associated component(s). For an component given by <@>, the associated component(s) are initialized by default (see 3.3.1). !corrigendum 4.8(5) @drepl If the type of the @fa is an access-to-constant type, the @fa shall be an initialized allocator. If the designated type is limited, the @fa shall be an uninitialized allocator. @dby If the type of the @fa is an access-to-constant type, the @fa shall be an initialized allocator. !corrigendum 7.5(1) @drepl 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. @dby 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. !corrigendum 7.5(2) @dinsb If a tagged record type has any limited components, then the reserved word @b shall appear in its @fa. @dinst For an assignment operation that initializes a limited object with the value of an @fa, the @fa shall be a (possibly parenthesized or qualified) @fa. !corrigendum 7.5(8) @dinsa There are no predefined equality operators for a limited type. @dinst @i<@s8> For an @fa of a limited type used to initialize an object as allowed above, the implementation shall not create a separate anonymous object for the @fa. The @fa shall be constructed directly in the new object. !corrigendum 7.5(9) @drepl @xindent<@s9<13 The following are consequences of the rules for limited types: >> @dby @xindent<@s9<13 While limited types have an assignment operation, other rules of the language insure that it is never actually invoked. The source of such an assignment operation must be an @fa, and such @fas must be built directly in the target object.>> !corrigendum 7.5(10) @ddel @xinbull<@s9 if the type of the object is limited.>> !corrigendum 7.5(11) @ddel @xinbull<@s9 if the type of the record component is limited.>> !corrigendum 7.5(12) @ddel @xinbull<@s9> !corrigendum 7.5(13) @ddel @xinbull<@s9 must not be of a limited type.>> !corrigendum 7.5(14) @ddel @xindent<@s9<14 @fas are not available for a limited composite type. Concatenation is not available for a limited array type.>> !corrigendum 7.5(15) @ddel @xindent<@s9<15 The rules do not exclude a @fa for a formal parameter of a limited type; they do not exclude a deferred constant of a limited type if the full declaration of the constant is of a nonlimited type.>> !corrigendum 9.1(21) @drepl @xindent<@s9<4 A task type is a limited type (see 7.5), and hence has neither an assignment operation nor predefined equality operators. If an application needs to store and exchange task identities, it can do so by defining an access type designating the corresponding task objects and by using access values for identification purposes. Assignment is available for such an access type as for any access type. Alternatively, if the implementation supports the Systems Programming Annex, the Identity attribute can be used for task identification (see C.7).>> @dby @xindent<@s9<4 A task type is a limited type (see 7.5), and hence has neither assignment nor predefined equality operators. If an application needs to store and exchange task identities, it can do so by defining an access type designating the corresponding task objects and by using access values for identification purposes. Assignment is available for such an access type as for any access type. Alternatively, if the implementation supports the Systems Programming Annex, the Identity attribute can be used for task identification (see C.7).>> !corrigendum 9.4(23) @drepl @xindent<@s9<15 A protected type is a limited type (see 7.5), and hence has neither an assignment operation nor predefined equality operators.>> @dby @xindent<@s9<15 A protected type is a limited type (see 7.5), and hence has neither assignment nor predefined equality operators.>> !corrigendum 12.4(8) @ddel The type of a generic formal object of mode @b shall be nonlimited. !ACATS test ACATS B and C-Tests should be constructed to test these rules. !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 <> Sorry I do not understand this at all. Why is it in the nature of a queue that copying them is invalid? **************************************************************** From: Robert Eachus Sent: Thursday, October 10, 2002 6:46 PM A stack may be used by multiple producers and consumers, or it may be used by a single entity for temporary storage. If a stack has a single customer, saving and restoring the state is a meaningful operation. With a queue, the producer and consumer are almost always separate entities. In fact, in a well defined queue, the producer and consumer are decoupled. That is what the queue does. So neither the producer or consumer has enough information to safely save and restore a queue. On the other hand it is often appropriate for a producer to close a queue and for the consumer to close its end of the queue when the queue is empty and closed by the producer. A circular buffer with a pair of co-routines is a different animal altogether. For example, a parser may have a circular buffer of tokens, and call the scanner/lexer to create more tokens when they are needed. In such a situation, either one could save and restore the circular buffer, because it is explicitly true that the producer and consumer cannot be active at the same time. So I guess it is all a manner of intent, but it maps into semantics. For a queue implemented using a circular buffer, the producer and consumer are unaware of the boundary conditions. If the queue is full, a producer wanting to push another object on the queue is blocked. The producer doesn't have logiic to deal with the situation, and in fact it should never "see" it. The same applies to consumer and an empty queue. I think I got a bit into this when discussing bounded vs. unbounded, limited and unlimited, and protected vs. unprotected. To me for stacks, limited, protected, unbounded and unlimited, unprotected, bounded are the interesting cases, the other combinations are of very little or no interest. **************************************************************** From: Robert Dewar Sent: Wednesday, October 9, 2002 7:18 AM <> What a narrow view!!! What about a work pile algorithm, where a single process processes items off the front of the queue adding elements to the end of the queue. This to me is an absolutely standard use of queues. In this context, copying a queue (e.g. for some kind of recursive save/restore) makes absolutely perfect sense. Plesae don't impose this narrow producer/consumer view of a queue. This is Ada myopia induced by excessive tasking :-) **************************************************************** 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 <> 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 *) **************************************************************** From: Dan Eilers Sent: Thursday, October 10, 2002 2:11 PM Randy wrote: > The big benefit is that it would allow deferred constants of limited types. But the !summary says nothing about constants, let alone deferred constants. The AI states: > 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. Is this proposed new use of subtype_marks intended to be applicable for aggregate component_associations of any type (including unlimited untagged types)? It refers to "same meaning" as ancestor_parts, but ancestor_parts must be tagged. It seems a little strange that subtype_marks are proposed to be used to mean the subtype's default initial value when used in one kind of expression context (aggregate component_associations), but not in other expression contexts. There might be occasions where one wants to refer to the default initial value of a subtype other than in an aggregate. It also seems a little strange to allow subtype_marks alone in expressions. I realize there is the precedent of extension aggregates, but I would prefer some sort of new attribute such as subtype'default_initialization. Otherwise, it isn't intuitively obvious what is going on. **************************************************************** From: Robert Duff Sent: Thursday, October 10, 2002 3:51 PM > > The big benefit is that it would allow deferred constants of limited types. > > But the !summary says nothing about constants, let alone deferred constants. Perhaps it should, but I didn't mention it because it's not really a new rule. The RM never explicitly forbids limited constants -- other rules conspire to make them impossible, and the AI happens to make them possible. > The AI states: > > 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. > > Is this proposed new use of subtype_marks intended to be applicable > for aggregate component_associations of any type (including unlimited > untagged types)? Yes. >...It refers to "same meaning" as ancestor_parts, but ancestor_parts > must be tagged. I meant the "same meaning" in terms of run-time semantics -- it gives you the default initial values. I did not mean to restrict it to tagged or limited types. This may not be clear from the summary, but I think it's clear from the proposed RM wording. > It seems a little strange that subtype_marks are proposed to be used > to mean the subtype's default initial value when used in one kind of > expression context (aggregate component_associations), but not in > other expression contexts. There might be occasions where one wants > to refer to the default initial value of a subtype other than in an > aggregate. I'm reluctant to allow it in other contexts. The point is to allow the *initialization* of an object (in this case, a component of an aggregate) using default initialization. Regular variable declarations already have this capability -- you say "X: T;", not "X: T := T;". I don't really see the point in allowing, for example, X := T; as an equivalent for: declare Temp: T; begin X := Temp; end; Also, I don't like to weigh down the proposal with extra bells and whistles -- it's hard enough to get the simplest, easiest to implement thing past the vendors (who understandably don't want to do useless work). > It also seems a little strange to allow subtype_marks alone in expressions. > I realize there is the precedent of extension aggregates, but I would > prefer some sort of new attribute such as subtype'default_initialization. > Otherwise, it isn't intuitively obvious what is going on. I agree -- it's a little strange. If it weren't for extension aggregates, I would look for different syntax. If we were designing the language from scratch, I might consider "X => default", where default is a keyword. But given the language as it is, I think it best to use the subtype_mark. **************************************************************** From: Robert Duff Sent: Monday, October 14, 2002 12:48 PM I've been trying to do my homework on AI-287, and I have some questions. AI-287 says that aggregates are allowed for limited types, and that a component of an aggregate can be specified by a subtype_mark instead of an expression. The subtype_mark thing is necessary in the limited case, because you can't use an expression for some limited component types (e.g., if the component of the aggregate is of a task type, or a limited private type -- you can't give an object name, because it's limited, and you can't give an aggregate, because it's not a record or array). For uniformity and simplicity, we also allow this notation for non-limited types. Note that this is not error-prone: the full-coverage rules are intended to prevent you from *accidentally* leaving some components undefined. But if you explicitly write the subtype_mark, you are explicitly asking for them to be left undefined, which is obviously not an accident. At the ARG meeting, we discussed the issue of how (or whether) to ensure that the constraints on this subtype_mark are appropriate. We never quite finished the discussion, but we seemed to be saying that for scalars, any constraint on the subtype_mark is ignored, and for composites, there is a run-time check. (We ignored access subtypes!) I have been thinking about this, and I now think think that a run-time check is inappropriate. I also dislike the "ignore" part. Run-time checks are only appropriate when they afford some flexibility -- i.e. when the corresponding compile-time check is too restrictive. But I can't think of any case where you would *want* a run-time check. Here's the legality rule I now want to propose: For a record_component_association with a subtype_mark that denotes a constrained subtype, this subtype shall statically match the subtype of the associated component(s). (and a similar rule for components of array aggregates). In other words, in the aggregate, you have to give either an unconstrained subtype, or a constrained one that is known at compile time to have the "right" constraints. Here are some examples: type Task_Type is task ...; subtype Task_Count is Integer range 1..10; type Task_Array is array (Task_Count range <>) of Task_Type; type Constrained_Task_Array is Task_Array(1..4); type R(N_Tasks: Task_Count := 0) is limited record Tasks: Task_Array(1..N_Tasks); More_Tasks: Constrained_Task_Array; Nat: Natural := 0; Int: Integer'Base := 123; Dynamic: Integer range 1..Some_Dynamic_Expression := 1; end record; X: R := (N_Tasks => 5, Tasks => Task_Array, More_Tasks => Constrained_Task_Array, Nat => Natural, Int => Integer'Base, Dynamic => Integer'Base); For the above, the task arrays *must* be given as subtype_marks; you can't give an object name or an aggregate. For Tasks, we want to be able to give the unconstrained subtype. It would be annoying to have to say: subtype Task_Array_5 is Task_Array(1..5); X: R := (..., Tasks => Task_Array_5, -- Illegal! ...); and then have a run-time check that Task_Array_5 has the right bounds. The proposed rule makes the above illegal -- you *have* to give the unconstrained array, because no constrained array can statically match a discriminant-dependent array subtype. (You could also say (..., Tasks => (others => Task_Type), ...), but that's beside the point.) For More_Tasks, you have a choice. You can say "More_Tasks => Constrained_Task_Array" or "More_Tasks => Task_Array". In the former case, we check at compile time that the bounds are correct; in the latter case, no particular bounds are implied by "Task_Array". Note that there is no need for "sliding": you can always just name the right constrained array subtype with exactly the right bounds, or else the unconstrained one. Anyway, I think sliding is a concept that applies to expressions and names -- not subtype_marks. Now, as to the scalar components. You don't *have* to use subtype_marks for these -- you would normally use expressions. But we have decided subtype_marks should be legal (and I think otherwise generic contract model problems would rear their ugly heads). Here, I require that you give an unconstrained subtype (implying no particular range), or the *right* constrained subtype. So: For Nat, you can say "Nat => Natural" or "Nat => Integer'Base" (or even "Nat => Natural'Base"). I think "Nat => Positive" would be confusing and error-prone, whether the constraint on Positive is ignored, or run-time checked against Natural. So "Nat => Positive" is illegal (at compile-time). For Int, you must say "Int => Integer'Base" -- again, giving a particular constraint would be confusing. For Dynamic, you must say "Dynamic => Integer'Base", because there is no subtype_mark that statically matches the anonymous subtype of Dynamic. If we did a run-time check, you could say: subtype My_Subtype is Integer range ...; ... Dynamic => My_Subtype ... but that would be *very* error prone, because you have to take care to make sure the dynamic bounds are the same. If you really want to give a named constrained subtype here, you really ought to declare it before type R, and use that same name as the subtype of Dynamic, and also in the aggregate. To obey the decision made at the ARG meeting to ignore scalar constraints, I would have to change "denotes a constrained subtype" to "denotes a constrained composite or access subtype" in the above rule. But I think the above discussion shows that there is no benefit to that added complexity -- there is always a handy subtype name lying around that you can use (either unconstrained, or with the *right* constraint). (By the way, I think whatever rule applies to composite has to apply to access -- bleah.) Note that "Nat => Natural" means to use the default initialization for Natural (i.e., leave Nat undefined). It does *not* mean to use the default expression for Nat given in the record type. That could be confusing -- is it the desired semantics? If not, what is? Now consider a record that wraps the above R: type R2 is record R_Comp: R; -- Note that R's discriminants are defaulted. ... other components end record; Y: R2 := (R_Comp => R, ...); Steve Baird suggested that the following should also be allowed: subtype My_R is R(N_Tasks => 7); Z: R2 := (R_Comp => My_R, ...); -- Illegal! My proposed rule above makes this illegal: My_R is constrained, and does not statically match R. If this were legal, it would presumably mean that Z.R_Comp.N_Tasks is initialized to 7 (from My_R), and the rest of Z.R_Comp's components are initialized by default. In other words, the same sort of initialization you would get if you declared a standalone object "Obj: My_R;". That made sense to me during the meeting, but now it seems weird -- the run-time semantics says Z.R_Comp is initialized by default, in other words just like "Obj: R;", which is a contradication. It also seems confusing to use My_R to *initialize* the discriminant of R_Comp, rather than to *constrain* it. Note that defaulted discriminants is the *only* case where the component can be unconstrained and you can get some useful information from a constrained subtype_mark. Instead, you can say: Z: R2 := (R_Comp => (N_Tasks => 7, Tasks => Task_Array, More_Tasks => Constrained_Task_Array, Nat => Natural, Int => Integer'Base, Dynamic => Integer'Base), ... -- other components of R2 ); But I have two problems with that: - It doesn't quite do the same thing: it leaves Nat undefined, whereas the previous (illegal) version of Z initializes Nat to 0. - What if, at the point of declaring R2 and writing the aggregate, R is a private view? Then we can't write the nested aggregate for R_Comp. In summary, I would like guidance from the ARG on the following points: - Am I correct that a static rule does not prevent anything useful? - Is the semantics of "Nat => Natural", leaving Nat undefined rather than using the default 0, what we want? - Do we need to support the functionality suggested by Steve Baird for defaulted discrims? **************************************************************** From: Robert Eachus Sent: Monday, October 14, 2002 2:27 PM Robert A Duff wrote: >I've been trying to do my homework on AI-287, and I have some >questions. > > As always the devil is in the details... >Run-time checks are only appropriate when they afford some flexibility >-- i.e. when the corresponding compile-time check is too restrictive. >But I can't think of any case where you would *want* a run-time check. > > I agree. The "constants of a limited type" argument has probably convinced me that this proposal has some merit, but run-time checking just seems stupid, when there will always be an unconstrained or statically matching subtype for the cases the ARG is trying to permit. >Now, as to the scalar components. You don't *have* to use subtype_marks >for these -- you would normally use expressions. But we have decided >subtype_marks should be legal (and I think otherwise generic contract >model problems would rear their ugly heads). Here, I require that you >give an unconstrained subtype (implying no particular range), or the >*right* constrained subtype. > This is a good point. We can't avoid the problem described below just by handwaving. It is always possible to instantiate a generic formal limited private type as a discrete type. So I have to assume that this proposal will allow something like: generic type Foo is limited private; package Bar; package body Bar is type Foob is limited record... Foobar: Foo; end record; FB: Foob := (..., Foo); end Bar; >Note that "Nat => Natural" means to use the default initialization for >Natural (i.e., leave Nat undefined). It does *not* mean to use the >default expression for Nat given in the record type. That could be >confusing -- is it the desired semantics? If not, what is? > Ouch! There are two possibilities here, first, for an aggregate to eliminate a default initial value is a bad change in the wrong direction. In fact, I think it has to be treated as an impossible solution. For example, what happens if you give a subtype mark in the position of a discriminant in a record aggregate? So I think that the only legitimate solution is to disallow subtype marks for any record component with an explicit initial value. It has to be a legality check, and notice that it only applies to components of the record type. (And to components in subaggregates.) Using a subtype mark for a record or record component that has (sub)components with default values is not a problem. I also think that methodologically the same rule should hold for array components in subaggregates: type Initialized_String (Length: Natural) is record Str: String(1..Length) := (others => ' '); end record; ... IS: Initialized_String := (10, String); Is just silly, not actively harmful, but I don't see any particular reason to allow it. Note that the case: IS: Initialized_String := (Natural, "This is some error message or something."); Might be nice to allow, but would need some more rule smithing. I am not recommending that it be allowed. **************************************************************** From: Robert Duff Sent: Monday, October 14, 2002 2:51 PM > Robert A Duff wrote: > > >I've been trying to do my homework on AI-287, and I have some > >questions. > > > > > As always the devil is in the details... Indeed. ;-) ... > >Now, as to the scalar components. You don't *have* to use subtype_marks > >for these -- you would normally use expressions. But we have decided > >subtype_marks should be legal (and I think otherwise generic contract > >model problems would rear their ugly heads). Here, I require that you > >give an unconstrained subtype (implying no particular range), or the > >*right* constrained subtype. > > > This is a good point. We can't avoid the problem described below just > by handwaving. It is always possible to instantiate a generic formal > limited private type as a discrete type. So I have to assume that this > proposal will allow something like: > > generic > type Foo is limited private; > package Bar; > > package body Bar is > type Foob is limited record... > Foobar: Foo; > end record; > FB: Foob := (..., Foo); > end Bar; Yes, this will be legal. If Foo turns out to be a task type, a new task is created. If Foo turns out to be an access type, then FB.Foobar is set to null. If Foo turns out to be Integer, then FB.Foobar is left undefined, as is usual for integers. > >Note that "Nat => Natural" means to use the default initialization for > >Natural (i.e., leave Nat undefined). It does *not* mean to use the > >default expression for Nat given in the record type. That could be > >confusing -- is it the desired semantics? If not, what is? > > > Ouch! There are two possibilities here, first, for an aggregate to > eliminate a default initial value is a bad change in the wrong > direction. I agree. The more I think about it, the more I think that "Nat => Natural" should mean that the default expression for Nat is used, if there is one, and if not, the default for Natural is used (which is to leave it undefined). It's *almost* as if you just declared an object of the type (and default initialized it), and then use assignment statements to initialize those components that were given as expressions in the aggregate -- that's the code you have to write without this AI. The difference is that this model would initialize some components twice, which is not desired. >...In fact, I think it has to be treated as an impossible > solution. For example, what happens if you give a subtype mark in the > position of a discriminant in a record aggregate? That is explicitly disallowed by the AI: 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. >... So I think that the > only legitimate solution is to disallow subtype marks for any record > component with an explicit initial value. It has to be a legality > check, and notice that it only applies to components of the record type. > (And to components in subaggregates.) Yeah, that's one possibility. But why not allow it, and say it *uses* the default declared for that component? >...Using a subtype mark for a > record or record component that has (sub)components with default values > is not a problem. Right. > I also think that methodologically the same rule should hold for array > components in subaggregates: > > type Initialized_String (Length: Natural) is record > Str: String(1..Length) := (others => ' '); > end record; > ... > IS: Initialized_String := (10, String); > > Is just silly, not actively harmful, but I don't see any particular > reason to allow it. I would say it should mean that IS.Str is initialized to all blanks. The alternative is to make it illegal, as you say. Having it ignore the all-blanks default seems like bad news. > Note that the case: > > IS: Initialized_String := (Natural, "This is some error message or > something."); > > Might be nice to allow, but would need some more rule smithing. I am > not recommending that it be allowed. Agreed. During the Ada 9X design, we tried to allow something similar -- namely, leaving out discriminants when they can be inferred from other components, but it didn't fly. If that's a good idea, it should be a separate AI -- I'm not proposing that here. **************************************************************** From: Tucker Taft Sent: Monday, October 14, 2002 3:01 PM I am now concerned that we allow explicitly creating record objects that have uninitialized components, when the components have default expressions. Until now, we could safely assume that a component with a default expression could *never* be uninitialized, and we took advantage of this in various checks. Essentially, we always "believe" the subtype of a component with a default expression, whereas we don't always "believe" the subtype of a component without a default expression. I see three solutions to this problem: 1) You *must* use an expression rather than a subtype_mark if the component has a default expression. I presume the requirement to use an expression rather than a subtype_mark would always apply to discriminants. or 2) Use the component's default expression, if any, when a subtype_mark is used for a component of an (extension or record) aggregate or 3) Allow a subtype_mark only for components for which an expression would be disallowed (limited private [extension], task, or protected). Note that this restriction would be inconsistent with extension aggregate ancestor parts. (1) is a bit odd, since it requires that you provide an explicit value exactly when there *is* a default defined. (2) would seem to make more sense to a user, and would preserve the desirable state (as far as the compiler, at least) that a component with a default is always initialized in all record objects. (2) is also closer to the semantics of extension aggregates, in that components in the ancestor part with defaults end up with those valeus. (3) allows the subtype_mark feature to be used only where it is absolutely needed, but that is inconsistent with the decision made w.r.t. extension aggregate ancestor parts, and seems unfriendly. (2) is the only one that allows subtype_mark to be used everywhere, and is from the user perspective, essentially equivalent to the components being uninitialized, except that the default expression will be evaluated (which might have side-effects or raise an exception). So I guess I favor (2). I suppose with (2), we still need to decide whether discriminants should be allowed to be defaulted. **************************************************************** From: Robert A. Duff Sent: Monday, October 14, 2002 3:46 PM > I am now concerned that we allow explicitly > creating record objects that have uninitialized > components, when the components have default > expressions. Until now, we could safely assume > that a component with a default expression could > *never* be uninitialized, and we took advantage of > this in various checks. Essentially, we always "believe" > the subtype of a component with a default expression, > whereas we don't always "believe" the subtype of a > component without a default expression. > > I see three solutions to this problem: > 1) You *must* use an expression rather than a subtype_mark > if the component has a default expression. I presume the > requirement to use an expression rather than a subtype_mark > would always apply to discriminants. This is what Robert Eachus suggested. It works, but I think (2) below is better. > or > 2) Use the component's default expression, if any, when a > subtype_mark is used for a component of an (extension > or record) aggregate This is what I (now) like. > or > 3) Allow a subtype_mark only for components for which an > expression would be disallowed (limited private [extension], > task, or protected). Note that this restriction would be > inconsistent with extension aggregate ancestor parts. I hadn't thought about that possibility. It works, but I still think (2) is best. > (1) is a bit odd, since it requires that you provide an explicit value > exactly when there *is* a default defined. From the programmer's point of view, it requires that you write the same thing twice. It seems to me that if I already wrote it on the component decl, I don't want to write it again (error-pronedly (is that a word?)) in an aggregate. I have written code where there's a component that must point to the whole record: type T is limited record Self: T_Ptr := T'Unchecked_Access; ... which is only allowed for limited types. I don't want to write it again in the aggregate, and I don't want it to use the "null" default for access types. So that again favors (2). >... (2) would seem to make more > sense to a user, and would preserve the desirable state (as far as the > compiler, at least) that a component with a default is always initialized > in all record objects. (2) is also closer to the semantics of extension > aggregates, in that components in the ancestor part with defaults end > up with those valeus. (3) allows the subtype_mark feature to be used > only where it is absolutely needed, but that is inconsistent with the > decision made w.r.t. extension aggregate ancestor parts, and seems > unfriendly. > > (2) is the only one that allows subtype_mark to be used everywhere, > and is from the user perspective, essentially equivalent to the components > being uninitialized, except that the default expression will be evaluated > (which might have side-effects or raise an exception). > > So I guess I favor (2). Me, too. I already wrote it up that way before I saw this message... > I suppose with (2), we still need to decide whether discriminants > should be allowed to be defaulted. ...and in my writeup, I decided that an expression is required for a discriminant only if it has no default. (The previous version of the AI always required expressions for discriminants, as requested by Gary Dismukes.) I think it's desirable to allow the discriminants to be defaulted because it's consistent with other components, and because sometimes discriminant defaults are just defaults (i.e. when they are access values) -- as opposed to the weird "I never want to use this default value, but I want the thing to allow unconstrained variables." If you don't like that, say so (and please explain why). - Bob P.S. I asked three questions in my e-mail. Robert Eachus answered two of them, and Tuck one of them (thank you both!), but I'd like to hear more, especially on number 3: 1. Am I correct that a static rule does not prevent anything useful? 2. Is the semantics of "Nat => Natural", leaving Nat undefined rather than using the default 0, what we want? (No.) 3. Do we need to support the functionality suggested by Steve Baird for defaulted discrims? **************************************************************** From: Robert A. Duff Sent: Monday, October 14, 2002 4:02 PM Tucker said: > > or > > 3) Allow a subtype_mark only for components for which an > > expression would be disallowed (limited private [extension], > > task, or protected). Note that this restriction would be > > inconsistent with extension aggregate ancestor parts. and I replied: > I hadn't thought about that possibility. It works, but I still think > (2) is best. Also, as Robert Eachus pointed out, rule (3) doesn't really prove anything, given that the actual for a generic formal limited private type might just be plain old Integer. And likewise, the full type for a package-limited-private type might be Integer. Just more arguments in favor of (2). > >... (2) would seem to make more > > sense to a user, and would preserve the desirable state (as far as the > > compiler, at least) that a component with a default is always initialized > > in all record objects. Yeah, "at least." That "desirable state" is something the *programmer* should be able to count on, never mind the compiler. **************************************************************** From: Tucker Taft Sent: Monday, October 14, 2002 9:09 PM [I thought I answered these, but I can't find my reply...] Robert A Duff wrote: >I've been trying to do my homework on AI-287, and I have some >questions. > >... > >In summary, I would like guidance from the ARG on the following points: > > - Am I correct that a static rule does not prevent anything useful? > I would prefer if you always allowed the first subtype. Requiring the use of the base type for scalars seems inconvenient and not too user friendly. The first subtype is what is recommended in a couple of places relating to renames, and it would be nice if the first subtype was generally considered "OK." > - Is the semantics of "Nat => Natural", leaving Nat undefined rather > than using the default 0, what we want? > As we all seem to agree, leaving Nat undefined is bad news. As you and I seem to agree, using the component default seems like a good solution. > > - Do we need to support the functionality suggested by Steve Baird > for defaulted discrims? > I don't think it is critical, but I think consistency with the semantics of ancestor subtype marks in extension aggregates argues for allowing it. It adds a little complexity to the wording, but the added consistency probably simplifies the user's job of remembering the rules. **************************************************************** From: Robert A. Duff Sent: Tuesday, October 15, 2002 9:18 PM > >In summary, I would like guidance from the ARG on the following points: > > > > - Am I correct that a static rule does not prevent anything useful? > > > > I would prefer if you always allowed the first subtype. Requiring > the use of the base type for scalars seems inconvenient and not too > user friendly. I think I disagree. Usually, you're going to use the exact same subtype name in both places (the component_decl and the aggregate). This is usually a named constrained subtype, and you don't need the unconstrained ('Base) subtype. > ...The first subtype is what is recommended in a couple > of places relating to renames, and it would be nice if the first subtype > was generally considered "OK." Well, those places are broken, IMHO. (E.g. the fact that subtypes are ignored in renaming decls.) So I don't like to pattern new language features after those. But I don't feel strongly about it. If folks think the first subtype should always be allowed, I give in. > > - Is the semantics of "Nat => Natural", leaving Nat undefined rather > > than using the default 0, what we want? > > As we all seem to agree, leaving Nat undefined is bad news. > As you and I seem to agree, using the component default > seems like a good solution. Agreed. > > - Do we need to support the functionality suggested by Steve Baird > > for defaulted discrims? > > I don't think it is critical, but I think consistency with the > semantics of ancestor subtype marks in extension > aggregates argues for allowing it. It adds a little > complexity to the wording, but the added consistency > probably simplifies the user's job of remembering the > rules. (Grumble.) I suppose you're right... **************************************************************** From: Pascal Leroy Sent: Tuesday, October 15, 2002 7:32 AM > 1) You *must* use an expression rather than a subtype_mark > if the component has a default expression. I presume the > requirement to use an expression rather than a subtype_mark > would always apply to discriminants. > > or > 2) Use the component's default expression, if any, when a > subtype_mark is used for a component of an (extension > or record) aggregate I find (2) quite weird; the notion that the subtype name is used to denote the default expression looks quite artificial to me. I would prefer (1) together with a special notation for denoting the default expression without having to write it a second time. For instance: X : R := (..., Nat => <>, ...); But I realize that this is new syntax, so it makes the whole proposal look more costly. (Well, it is not entirely new syntax if we adopt AI 317 ;-) **************************************************************** From: Tucker Taft Sent: Tuesday, October 15, 2002 8:02 AM > > 1) You *must* use an expression rather than a subtype_mark > > if the component has a default expression. I presume the > > requirement to use an expression rather than a subtype_mark > > would always apply to discriminants. > > > > or > > 2) Use the component's default expression, if any, when a > > subtype_mark is used for a component of an (extension > > or record) aggregate > > I find (2) quite weird; the notion that the subtype name is used to denote the > default expression looks quite artificial to me. (2) isn't really that weird. It says "do the usual default initialization for this component" which means use the specified component default, if any, or do type-based default initialization if no component default is specified. > ...I would prefer (1) together > with a special notation for denoting the default expression without having to > write it a second time. For instance: > > X : R := (..., Nat => <>, ...); > > But I realize that this is new syntax, so it makes the whole proposal look more > costly. (Well, it is not entirely new syntax if we adopt AI 317 ;-) This might be killing the proposal with kindness ;-). If you look at the semantics associated with the extension aggregate subtype_mark, it supports the general idea of using component defaults (though I realize it is represented by the enclosing subtype_mark rather than the component subtype_mark). To me, (1) is quite weird, because it forces the programmer to provide explicit expressions exactly where there are defaults, which is the *reverse* of all other similar situations. An alternative approach to this whole AI is to drop the use of the subtype_marks completely, and go to allowing "others => <>" meaning that unspecified components are initialized according to their defaults. This loses the full coverage feature, however. I suppose once we drop the subtype_mark idea, then also allowing "<>" for an individual component as you suggest would also make sense. If we do that, then we should probably allow that in AI-317 as well (which currrenly only allows "others => <>"). This would enshrine "<>" to mean "unspecified" in general. Personally, I would only allow using "<>" with named or others notation. "<>" by itself should be reserved for meaning that the entire list of parameters/discriminants/ components is unspecified, as it is now in unknown discriminant parts, formal scalar types (list of enumerals omitted), and formal packages. In other words, "<>" by itself means "others => <>" and may only be used if it is the only thing in the parens. With this approach, you would get full coverage checking if you use named notation without others. So I guess this means we now have an alternative (4): (4) Use "<>" rather than subtype_marks for unspecified components. Require named or others notation, unless "<>" is the only thing in the aggregate. "(<>)" would be a fully-default-initialized record or array. Hmmmm.... **************************************************************** From: Pascal Leroy Sent: Tuesday, October 15, 2002 7:32 AM > An alternative approach to this whole AI is to drop the > use of the subtype_marks completely, and go to allowing > "others => <>" meaning that unspecified components are > initialized according to their defaults. This loses the > full coverage feature, however. No, I think losing the full coverage would be really bad. I have been bitten in the past with record aggregates having things like "others => False", and I added a new boolean component, but False was not appropriate and bad things happened. What you suggest would only make matters worse. So I would definitely disallow "others => <>". **************************************************************** From: Tucker Taft Sent: Tuesday, October 15, 2002 8:27 AM It seems odd to take this paternalistic attitude now, given that case statements and aggregates both already allow "others =>". I would agree that a programmer should be able to get default initialization for some components without needing "others" (e.g. by adopting your suggestion for <> for individual components), but to say that it is always bad to use "others" seems presumptious, and more importantly, inconsistent. Clearly, in a given context or on a given project, it might make sense to allow or disallow use of others, but that goes for the other places where it is permitted as well. **************************************************************** From: Robert Dewar Sent: Tuesday, October 15, 2002 8:32 AM I entirely agree. The language design has already decided that others is a first class citizen. If you don't like it, look to your coding standards and other tools to enforce your tastes, not the Ada standard. **************************************************************** From: Tucker Taft Sent: Tuesday, October 15, 2002 8:52 AM Pascal Leroy wrote: > I have been bitten in >the past with record aggregates having things like "others => False", and I >added a new boolean component, but False was not appropriate and bad things >happened. What you suggest would only make matters worse. So I would >definitely disallow "others => <>". Actually, "others => <>" might generally be safer than "others => false" since the person who adds the component would determine the default value (true or false) rather than the person writing the aggregate with others. I have always found the "others" feature in record aggregates a bit odd, in that it only works for components of all the same type. Something like others => <> could actually be useful, if you are in a situation where you want "poor man's" extensibility (i.e. by using a text editor on the record definition) without affecting client code. On the other hand, if you *want* all clients to be forced to deal with the new component, then enforce a project standard that disallows use of others. Allowing "others => false" but disallowing "others => <>" seems weird, given the greater safety provided by "others => <>" to the type "extender." **************************************************************** From: Pascal Leroy Sent: Tuesday, October 15, 2002 9:07 AM Ok, that's a pretty convincing argument, I withdraw the objection. **************************************************************** From: Robert A. Duff Sent: Tuesday, October 15, 2002 9:38 AM > I find (2) quite weird; the notion that the subtype name is used to denote the > default expression looks quite artificial to me. I would prefer (1) together > with a special notation for denoting the default expression without having to > write it a second time. For instance: > > X : R := (..., Nat => <>, ...); Hmm. This works, but it seems to add complexity, both in the language and for the programmer. I'm against adding unnecessary complexity to this AI, because I want it to succeed! Perhaps the following will make you more comfortable with the "naturalness" of using a subtype_mark to mean "use the default_expression": It seems to me that this is how the programmer should think: In Ada 95, one might write: type Some_Limited_Type is limited record Tasks: Task_Array; X: Integer := 123; Y: Boolean := False; end record; X: Some_Limited_Type; -- X.Tasks is default initialized. ... X.This := 456; -- Leave X.That default-initialized. In the new Ada with AI-287, one can instead write this: X: Some_Limited_Type := (Tasks => Task_Array, This => 456, That => Boolean); which has *exactly* the same run-time semantics (the only difference being the extra compile-time check). To me, it makes perfect sense that the subtype_mark notation means "for this component, give me whatever I would have gotten if I used the old way of doing it". Of course, if the default_expressions in the record type have side effects, and those side effect matter to the output of the program, then the second is not exactly equivalent to the first. But that's got to be rare -- most programmers don't want object declarations to go around "doing stuff"; they want to calculate values to put in the components. So, it seems to me, we want a *single* notation that says "give me the component's default, if there is one, and the component's subtype's default otherwise". This is just about the same as "give me what I would get if I declared an object of the aggregate's type, and then filled in some of the components with assignment statements". I don't much care what that notation is. I would say it should be "Nat => default", where default is a reserved word. But then the reserved word police would have me jailed (and rightly so). If you want "<>", I could go along with that if we then eliminate the subtype_mark notation and *always* use "<>". ("<>" already means 37 things in Ada -- I suppose one more won't hurt.) > But I realize that this is new syntax, so it makes the whole proposal > look more costly. (Well, it is not entirely new syntax if we adopt AI > 317 ;-) Depends what you mean by "new syntax". The AI already proposes to modify the syntax rules in the RM, even without the "<>" idea. But you don't have to change your compiler's parser, because subtype_mark is indistinguishable from expression (at least for most parsing techniques ;-)). If we add "<>", parsers must change, but I don't think that's a big deal. **************************************************************** From: Robert A. Duff Sent: Tuesday, October 15, 2002 9:57 AM > (2) isn't really that weird. It says "do the usual default > initialization for this component" which means use the > specified component default, if any, or do type-based default > initialization if no component default is specified. I agree. > > ...I would prefer (1) together > > with a special notation for denoting the default expression without having to > > write it a second time. For instance: > > > > X : R := (..., Nat => <>, ...); > > > > But I realize that this is new syntax, so it makes the whole proposal look more > > costly. (Well, it is not entirely new syntax if we adopt AI 317 ;-) > > This might be killing the proposal with kindness ;-). That's what worries me. We add more bells and whistles to language proposals until they're too big to swallow. (Here, the bell or whistle is to have two notations for doing essentially the same thing -- "use the normal default".) > Personally, I would only allow using "<>" with named > or others notation. I agree it has to be that way, but for me, that kills the idea. Much as I love named notation, it's not *always* appropriate. And if we use "<>" I want it to entirely replace the subtype_mark notation. Note that using some notation other than subtype_mark would simplify the rules about how the constraints have to match (by eliminating those rules!). Note that such a notation would lose the capability of specifying the discriminants, which Steve Baird suggested at the ARG meeting, and Tucker is on record as advocating. > So I guess this means we now have an alternative (4): > > (4) Use "<>" rather than subtype_marks for unspecified components. > Require named or others notation, unless "<>" is the only > thing in the aggregate. "(<>)" would be a fully-default-initialized > record or array. I don't much like the (<>) part. On the other hand, it's no worse that just declaring an object. That is, Some_Procedure((<>)); would be exactly equivalent to: X: T; Some_Procedure(X); Hmm. I think we should stick with the idea we have (subtype_mark), and make it mean what we want it to mean. **************************************************************** From: Robert A. Duff Sent: Tuesday, October 15, 2002 9:58 AM > No, I think losing the full coverage would be really bad. I have been > bitten in the past with record aggregates having things like "others => > False", and I added a new boolean component, but False was not > appropriate and bad things happened. What you suggest would only make > matters worse. So I would definitely disallow "others => <>". I agree. The whole point of this AI is to get full coverage for constructors of limited types, which we already know is a big win in the nonlimited case. Of course, records already allow "others =>" notation. Perhaps that was a mistake in Ada 83, but we're obviously not going to fix that now. I think we should leave the rules for others alone -- namely, that in a record, you can only give others if the components are all the same type. And programmers should know that you should rarely use others (not just in aggregates, but case statements as well). "Others" really means "all the others, including the ones that have not yet been invented". So you should not use others as a shorthand for "all the rest that I don't feel like typing in". You should use others only if you're very sure that all new cases invented in the future will necessarily fall into the same category. That's rare. For uniformity, I think we should allow "others => subtype_mark" (or "others => <>") in the cases where others is already allowed. We should not extend the rules (as I think Tucker was suggesting) to allow "others => <>" in a record aggregate to mean *all* the other components, no matter what their type. On the other hand, there's no harm in "others" being available if you obey my stylistic rule above (i.e. hardly ever use it). It's not like you can use it by accident. **************************************************************** From: Randy Brukardt Sent: Friday, October 25, 2002 10:09 PM Sorry about coming to this discussion so late; I missed the original one completely and just noticed it now as I was filing mail... > If you want "<>", I could go along with that if we then eliminate the > subtype_mark notation and *always* use "<>". ("<>" already means 37 > things in Ada -- I suppose one more won't hurt.) Yes, yes, yes!!!! > > But I realize that this is new syntax, so it makes the whole proposal > > look more costly. (Well, it is not entirely new syntax if we adopt AI > > 317 ;-) > > Depends what you mean by "new syntax". The AI already proposes to > modify the syntax rules in the RM, even without the "<>" idea. > But you don't have to change your compiler's parser, because > subtype_mark is indistinguishable from expression (at least for most > parsing techniques ;-)). If we add "<>", parsers must change, but I > don't think that's a big deal. I'd much rather have new syntax rather than subtype_marks here. That's because syntax means adding a couple of rules to the parser file, and regenerating. OTOH, the 'subtype_mark' proposal means fiddling around with name resolution. Other places where subtypes or expressions are allowed are a real pain, and this one is a much more common place. Moreover, using <> instead of a subtype_mark eliminates all of Bob's questions (and the associated RM verbiage). It seems silly to write a page of RM rules to avoid adding one new syntax rule. I also am suspicious about the supposed similarity to extension aggregates. You need the subtype mark there, because it can be any ancestor type - and you need to specify which one. But, Bob's proposal essentially only allows a single subtype (or one that happens to statically match) -- it provides no information whatsoever. So that could be a lot to type and read without giving any information to the compiler - real type names tend to be a lot longer than the ones in these examples. If I was doing this, I would use '<>' simply to replace all of the places where subtype_marks can be used in Bob's proposal, and no more (with one possible exception). That means that we don't lose positional notation. (10, <>, True) should be legal. The only 'extension' I'd consider is allowing it to match multiple types in others. The current rule for others in a record is already more restrictive than necessary (others => 0) is illegal for a record which has two components of different integer types. And it does seem appealing to be able to specify that an entire record, or most of it, is default initialized: P (Some_Record'(others => <>)); Q (Some_Record'(Cnt => 10, others => <>)); I think people would find it weird if this was required to only be used for components of the same type, because nowhere does the type appear. Moreover, it actually would make 'others' in a record aggregate useful for something. But I would not insist on it; I'd rather get the rest of the proposal. **************************************************************** From: Mike Yoder Sent: Saturday, October 26, 2002 1:22 AM >The only 'extension' I'd consider is allowing it to match multiple types in >others. The current rule for others in a record is already more restrictive >than necessary (others => 0) is illegal for a record which has two >components of different integer types. > Well, I presume you have some sort of special case check in mind here, otherwise I'd say the current rule isn't too restrictive. Say I wrote (others => f(x) + g(y)) for a record type with 6 components, all of different types, and all leading to different legal overload resolutions of the expression. I presume this isn't part of what you want, but even if the expression is a literal it would have to be a multitype expression with multiple simultaneous overload resolutions. If you only allow literals, how about (others => foo) where foo represents 6 overloaded enumeration literals? (The same is possible with character literals.) I suppose <> is different if there's no need to attach a type to it. **************************************************************** From: Robert A. Duff Sent: Sunday, November 3, 2002 9:51 AM Here is a new version of AI-287, "Limited Aggregates Allowed". Please review. The main change is to use the notation "<>" to mean "use the defaults", instead of a subtype_mark for the same purpose. This syntax was suggested by Pascal Leroy, if I remember correctly. "Use the defaults" means to use the default for a component_decl, if such a default exists, and to use default initialization for the type of the component otherwise. I believe I have taken into account the discussion at the Bedford ARG meeting, plus all the comments received in the last few weeks. [Editor's note: This is AI95-00287/02.] **************************************************************** From: Tucker Taft Sent: Sunday, November 3, 2002 11:52 AM I found one case where the "old" notation snuck through (see below). [Editor's note: this was corrected in the posted draft /02.] Also, you should at least mention the connection between this proposal and the wider use of "<>" with formal packages. In addition, presumably "(<>)" is not a legal 1-element aggregate, since "(blah)" is not a legal aggregate (it is a parenthesized expression). Alternatively, "(<>)" is interpreted specially as meaning "(others => <>)", which is really only justified if the analogy with formal discrete types or formal packages is drawn. **************************************************************** From: Robert A. Duff Sent: Sunday, November 3, 2002 12:25 PM > I found one case where the "old" notation snuck through > (see below). Thank you. > Also, you should at least mention the connection between > this proposal and the wider use of "<>" with formal > packages. I did: Static Semantics A record_component_association_list of the form <> is equivalent to others => <>. AARM Annotation: Reason: AI-317 proposes to allow "others => <>" in a formal_package_actual_part, and then goes on to define the existing syntax "(<>)" to be a shorthand for "(others => <>)". For consistency, we define the same shorthand notation here. If we were starting the language design over from scratch, I suspect we would allow *only* the "others => <>" notation in both cases. ... An array_aggregate of the form (<>) is equivalent to (others => <>). > In addition, presumably "(<>)" is not a legal 1-element aggregate, > since "(blah)" is not a legal aggregate (it is a parenthesized > expression). Alternatively, "(<>)" is interpreted specially > as meaning "(others => <>)", which is really only justified if > the analogy with formal discrete types or formal packages is > drawn. The syntax rules I wrote allow (<>) as a record aggregate or array aggregate, and it means the same as (others => <>). Also, (T with <>) is an extension aggregate meaning (T with others => <>). I thought that's what you were pushing for last time we talked about it, because that's waht AI-317 wants. I don't much like it, but I'm not sure why. (others => <>) just seems more readable to me that plain (<>). What do others folks think? By the way, I see now that I did it wrong: Modify this to allow (<>), which is a shorthand for (others => <>): record_component_association_list ::= record_component_association {, record_component_association} | null record | (<>) This should be: record_component_association_list ::= record_component_association {, record_component_association} | null record | <> since the parens come from the outer syntax rules. [Editor's note: This correction also was made in the posted draft /02.] **************************************************************** From: Tucker Taft Sent: Tuesday, November 19, 2002 9:02 AM An interesting ramification of the proposed amendment allowing aggregates for limited type initialization is that it would now be possible to have limited *constants*. Those don't exist in Ada 83 or Ada 95 (unless the full type is non-limited). This implies that tricks that treat a limited "in" object as a variable are in jeopardy of doing even more harm. Conceivably the trick that uses 'unchecked_access to generate a self-pointer could be disallowed for constants. What this effectively means is that the programmer could not use "<>" for the self-pointer component of the aggregate -- they would have to provide some explicit value (which would necessarily not be a self-pointer). Note that controlled objects are already a bit "funny" in this sense, since the Finalize routine is allowed to write on the object, even if it is a constant. However, in this case, the object is at least constant throughout its "normal" lifetime. **************************************************************** From: Robert Dewar Sent: Tuesday, November 19, 2002 6:20 PM Obviously such low level techniques have to be used carefully, just like Unchecked_Conversion. After all UC can do unlimited harm also, let's keep things in perpsective :-) **************************************************************** From: Randy Brukardt Sent: Thursday, January 16, 2003 5:15 PM > It might also help solve the dilemma caused by AI-287 and AI-318 > proposing to allow constants of limited types in the face of the > Rosen trick allowing writing on objects of limited types. Perhaps > the Rosen trick could be disallowed on limited types that didn't > have this pragma applied, although that would be an incompatibility. I don't see any dilemma here. We essentially have two options: -- Limited constants aren't quite constant in the case of the Rosen idiom (I don't think it is a trick - it's pretty much the intended use of the feature). That means that compilers need to be careful of how they're allocated, but that's pretty much required anyway as these are always by-reference types. That is compatible with the current language, which already has holes allowing constant views to be written (both for in parameters, and for access-to-constant types, where the same approach would apply). Those holes weren't plugged, I suppose, because there weren't any "real" constants -- these objects are all variables when they are declared. Which leads to the second option: -- Disallow constants of types using the Rosen idiom or any of its variants. That essentially means types with a self-referencing access attributes. However, that can't be done at compile-time, because that would be a (severe) privacy violation. A self-reference access in a limited constant is essentially an access-to-constant. So there could be a run-time check that it isn't used as an access-to-variable (which includes the access discriminant of the Rosen idiom). Then any attempt to declare a constant of such a type would raise Constraint_Error. Having it be a run-time check is nice in the sense that it provides a way to have such self-referencing types and still have constants: package P is type Interesting (<>) is limited private; Null_Interesting : constant Interesting; -- Operations on Interesting. private type I_Access is access all Interesting; type Interesting (Is_Null : Boolean) is limited record case Is_Null is when True => null; when False => Self : I_Access := Interesting'Unchecked_Access; end case; end record; Null_Interesting : constant Interesting := (Is_Null => True); end P; On the other hand: Very_Interesting : constant Interesting := (Is_Null => False, Self => <>); would raise Constraint_Error, as Self would be putting an access-to-constant into an access-to-variable (and we can't allow that). The check would have to apply to all subcomponents of the type (there can't be a workaround). Since access discriminants are defined to be access-to-variable types, the check would also apply to them. This doesn't seem too bad. But I haven't worked out all of the details. Hopefully Bob Duff (the author of this AI) is listening, because it is clear that this issue needs to be discussed in the AI. **************************************************************** From: Dan Eilers Sent: Friday, January 17, 2003 10:42 AM Randy wrote: (option 1) > -- Limited constants aren't quite constant in the case of the Rosen idiom (I > don't think it is a trick - it's pretty much the intended use of the > feature). That means that compilers need to be careful of how they're > allocated, but that's pretty much required anyway as these are always > by-reference types. By "careful of how they're allocated" do you mean that compilers shouldn't allocate the proposed limited-type constants in read-only memory? If so, then what exactly is the purpose such constants? Is it just to get writeable deferred constants? If so, it would probably make more sense to allow variables with deferred initialization. **************************************************************** From: Randy Brukardt Sent: Friday, January 17, 2003 1:26 PM Right. Or fail to actualize them altogether (Janus/Ada doesn't declare an object for certain small constants at all, creating them only when they're used.) But you have to actualize by-reference objects, so I don't think that is the problem. > If so, then what exactly is the purpose such constants? Is it just to get > writeable deferred constants? If so, it would probably make more sense > to allow variables with deferred initialization. IMHO, there is no purpose to such constants. We just can't ban them for the reasons mentioned in the first note, and the runtime check I outlined there doesn't seem implementable if a function is used to initialize them. The purpose of limited constants are for the 99% of limited types that don't use the Rosen idiom. Those are really constant. I know that I've never used an access of the enclosing type in all of my years of programming -- because Janus/Ada blew up with an internal error on any attempt to do it until recently. One could probably see the same issues in a task constant. If the task object is in fact a copy of the TCB (that's not how Janus/Ada works), then it could change while the task runs. But that's not important to the abstraction: what is important is that the task identified never changes. (In that sense, all task objects are constants.) It's a much too implementation-oriented view to say that some of the bits might change, so its not a constant. The important thing is that it always represent the same value. **************************************************************** From: Dan Eilers Sent: Friday, January 17, 2003 3:33 PM If I understand you correctly, you are proposing that limited constants would be allowed, even those that use the Rosen idiom, but the compiler could allocate a limited constant in ROM if and only if it could detect that the type didn't use the Rosen idiom. Would such Rom-able constants be considered pre-elaborable? **************************************************************** From: Robert A. Duff Sent: Monday, February 3, 2003 3:39 PM Here is a new version of AI-287, "Limited Aggregates Allowed". [Editor's not, this was version /03.] As usual, I apologize for doing my homework so late. I hope Pascal can put this on the agenda for the Padua meeting. I really hope that this AI makes it into the upcoming language revision. It might be nice to get AI-318 in as well, but I fear AI-318 involves too much implementation burden, so I want to see this AI considered separately from AI-318. I'm not even sure I will bother revising AI-318 before the Padua meeting. This is nearly identical to the previous version, which I sent out on November 3. The differences are: - Corrected the syntax of record_component_association_list. - Corrected the example New_T function to use the <> syntax. - Added some words to the discussion about the interaction with the "Rosen trick", as Dan Eilers called it. - Added some discussion about why we should tolerate passing this AI without passing AI-318. - Fix some typos. **************************************************************** From: Randy Brukardt Sent: February 19, 2003 I've made some minor wording changes from those in the approved AI in order to improve the presentation: -- I've replaced "box" with <> in the text. This eliminates the need for a naive reader to look in the index to find 3.6(15) to find out that <> is called box. There doesn't seem to be any advantage to using the term here rather than the syntax itself. -- I've added the following note to 7.5: "While limited types have an assignment operation, other rules of the language insure that it is never actually invoked. The source of such an assignment operation must be an aggregate, and such aggregates must be built directly in the target object." This is an attempt to explain the schizoid discussion of the assignment operation to the end users reading the standard. We really do not want anyone to think that assignment=>copying=>copying of limited types. We can't reinforce that enough (see the e-mail exchanges in the !appendix). -- I had problems with Bob's unhelpful "The notes of 7.3.1(12), 9.1(21), and 9.4(23) need minor rewording." When Bob writes something like that, he really means "I think this should be changed, but I don't have the slightest idea of what to write." It's even more confusing, because its not at all clear what he thought needed to be changed. Here are the notes in question: 9 Partial views provide assignment (unless the view is limited), membership tests, selected components for the selection of discriminants and inherited components, qualification, and explicit conversion. 4 A task type is a limited type (see 7.5), and hence has neither an assignment operation nor predefined equality operators. If an application needs to store and exchange task identities, it can do so by defining an access type designating the corresponding task objects and by using access values for identification purposes. Assignment is available for such an access type as for any access type. Alternatively, if the implementation supports the Systems Programming Annex, the Identity attribute can be used for task identification (see C.7). Examples 15 A protected type is a limited type (see 7.5), and hence has neither an assignment operation nor predefined equality operators. I presume that Bob was concerned that each of these notes says that the type in question doesn't have an assignment. The first note, which just says 'assignment', is vague enough to leave alone, as limited types have assignment in name only. Trying to be more precise here is more likely to confuse the reader than help. One of the reasons for adding the note in 7.5 is to allow us to say that limited types don't have assignment in non-normative text, which is far more likely to be helpful to the reader than an pedantic explanation would be. The second two notes say that the types don't have an 'assignment operation'. I've changed these to the intentionally vaguer 'assignment'. I can't think of any wording other wording which is precise enough, is simple enough to be a useful note, and still conveys the right meaning. "A protected type is a limited type (see 7.5), and hence does not support copying nor predefined equality operators." is a bizarre mixture of precise terms ("predefined equality operators") and an undefined concept ("copying"). "A protected type is a limited type (see 7.5), and hence does not support assignment_statements nor predefined equality operators." is over-specification (most other assignment operations are not allowed, either). ****************************************************************