!standard 4.10(6/5) 21-06-07 AI12-0435-1/03 !standard 4.10(15/5) !standard 4.10(26/5) !standard 13.13.2(38/4) !class Amendment 21-05-29 !status Amendment 1-2012 21-06-07 !status ARG Approved 12-0-2 21-06-03 !status work item 21-05-29 !status received 21-05-29 !priority Low !difficulty Easy !subject Fixups from WG 9 Issue #143 - Image and streams !summary (1) Exclude null extensions from the Implementation Advice about type extensions. (2) Define the subtype of the parameters to the default Put_Image implementations similarly to that for the stream-oriented attributes. (3) The subprogram specified for Put_Image cannot be abstract. (4) Stream-oriented attributes are implicitly composed. !problem (1) It seems desirable that if we have a tagged specific noninterface type T1 and a specific type T2 which is a proper descendant of T1 (i.e., a descendant of T1 other than T1 itself), and if T2 is "the same" as T1 ("the same" meaning that no new components are defined nor Put_Image redefined for T2), then for any value X of T2 it should be the case that T2'Image (X) = T1'Image (T1 (X)). This is not true when T2 is a null extension of T1. If T1'Image (X) = "(Some Image Text)", then (according to the implementation advice) T2'Image (T2 (X)) should equal something like "((Some Image Text) with NULL)". (2) Are rules for determining the parameter subtypes for T'Put_Image procedures sufficiently well-defined? For example, is the "others" aggregate in the following code legal? type T is array (1 .. 10) of Integer; -- first subtype is constrained procedure Foo (Buff : in out Ada.Strings.Text_Buffers.Root_Buffer_Type'Class) is begin T'Put_Image (Buff, (others => 123)); -- legal? end Foo; (3) For streams, we have a rule The subprogram name given in such an attribute_definition_clause or aspect_specification shall statically denote a subprogram that is not an abstract subprogram. We don't have an similar rule for Put_Image. Since we want the image of any type (including abstract types) to be well-defined, this seems to be an oversight. (4) we have rules in 13.1.1 and 12.5.1 that apply to aspects that are "implicitly composed", but we have no explicit statements anywhere in the manual that any particular aspects are "implicitly composed". This should be stated explicitly for Put_Image and for the aspects corresponding to the stream-oriented attributes. !proposal (See Summary.) !wording (1) Modify 4.10(6/5): For an untagged derived type{, or a null extension}, the default implementation of T'Put_Image invokes the Put_Image for its parent type on a conversion of the parameter of type T to the parent type. Modify 4.10(15/5): For a {nonnull} type extension, the default implementation of T'Put_Image depends on whether there exists a noninterface ancestor of T (other than T itself) for which the Put_Image aspect has been directly specified. If so, then T'Put_Image will generate an image based on extension aggregate syntax where the ancestor type of the extension aggregate is the nearest ancestor type whose Put_Image aspect has been specified. If no such ancestor exists, then the default implementation of T'Put_Image is the same as described below for a nonderived record type. (2) and (3) Add after 4.10(26/5): In the parameter_and_result_profile for the default implementation of Put_Image, the subtype of the Arg parameter is the base subtype of T if T is a scalar type, and the first subtype otherwise. For an aspect_specification or attribute_definition_clause specifying Put_Image, the subprogram name shall denote a nonabstract procedure whose second parameter is either of the first subtype of T, or as an option when T is scalar, the base subtype of T. (4) Modify 13.13.2(38/4): The stream-oriented attributes may be specified for any type via an attribute_definition_clause. Redundant[Alternatively, each of the specific stream-oriented attributes may be specified using an aspect_specification on any type_declaration, with the aspect name being the corresponding attribute name.] Each of the class-wide stream-oriented attributes may be specified using an aspect_specification for a tagged type T using the name of the stream-oriented attribute followed by 'Class; such class-wide aspects do not apply to other descendants of T. {If not directly specified, a default implementation of a stream-oriented attribute is implicitly composed for a nonlimited type, and for certain limited types, as defined above.} !discussion (1) Its unclear to the Editor whether this is really a principle that we want to support. OTOH, he has consistently objected to treating null extensions specially and has lost that argument consistently. (2) We copy the definition of stream-oriented attributes for Put_Image. (3) We never want Put_Image to be uncallable, so abstract routines should be banned. There's no problem with null procedures (even though they don't seem very useful in this context), so we allow them. (4) 4.10(4/5) states that Put_Image is implicitly composed. We don't have such wording for stream-oriented attributes, though. !corrigendum 4.10(0) @dinsc See the conflict file for the changes. !corrigendum 13.13.2(38/4) @drepl The stream-oriented attributes may be specified for any type via an @fa. Alternatively, each of the specific stream-oriented attributes may be specified using an @fa on any @fa, with the aspect name being the corresponding attribute name. Each of the class-wide stream-oriented attributes may be specified using an @fa for a tagged type @i using the name of the stream-oriented attribute followed by 'Class; such class-wide aspects do not apply to other descendants of @i. @dby The stream-oriented attributes may be specified for any type via an @fa. Alternatively, each of the specific stream-oriented attributes may be specified using an @fa on any @fa, with the aspect name being the corresponding attribute name. Each of the class-wide stream-oriented attributes may be specified using an @fa for a tagged type @i using the name of the stream-oriented attribute followed by 'Class; such class-wide aspects do not apply to other descendants of @i. If not directly specified, a default implementation of a stream-oriented attribute is implicitly composed for a nonlimited type, and for certain limited types, as defined above. !ASIS No ASIS effect. !ACATS test ACATS B-Tests should be constructed to test the subtypes of the parameters of Put_Image and to check that abstract subprograms cannot be specified. ACATS C-Tests should be constructed to check that null extensions do not affect the image. !appendix From WG 9 review issue #143 - Steve Baird It seems desirable that if we have a tagged specific noninterface type T1 and a specific type T2 which is a proper descendant of T1 (i.e., a descendant of T1 other than T1 itself), and if T2 is "the same" as T1 ("the same" in a sense I'll describe in a moment), then for any value X of T2 it should be the case that T2'Image (X) = T1'Image (T1 (X)). When I say that T1 and T2 are "the same", I mean that neither T2 nor any other proper descendant of T1 that is also an ancestor of T2 either defines any new components (including discriminants); or has an explicitly-specified Put_Image aspect. This seems to generally be true, but (unfortunately) not always. Consider the case where a type T1 has a user-defined Put_Image aspect and a type T2 is a null extension of T1, declared as type T2 is new T1 with null record; The implementation advice about how to handle this case does not have a special case for null extension (or a null extension of a null extension, etc.). So if T1'Image (X) = "(Some Image Text)", then (according to that advice) T2'Image (T2 (X)) should equal something like "((Some Image Text) with NULL)". That's not what we want. === Are rules for determining the parameter subtypes for T'Put_Image procedures sufficiently well-defined? [Tuck raised this question.] For example, is the "others" aggregate in the following code legal? type T is array (1 .. 10) of Integer; -- first subtype is constrained procedure Foo (Buff : in out Ada.Strings.Text_Buffers.Root_Buffer_Type'Class) is begin T'Put_Image (Buff, (others => 123)); -- legal? end Foo; === For streams, we have a rule The subprogram name given in such an attribute_definition_clause or aspect_specification shall statically denote a subprogram that is not an abstract subprogram. The word "abstract" does not appear anywhere in 4.10; that seems like a problem. We want to prohibit something like type T is tagged record ... end record with Put_Image => Foo; procedure Foo () is abstract; Assuming such a rule, would a (redundant) note be useful pointing out that Some_Abstract_Type'Put_Image is never an abstract procedure? === On a related note, we have rules in 8.6 that apply to aspects that are "implicitly composed", but we have no explicit statements anywhere in the manual that any particular aspects are "implicitly composed". This should be stated explicitly for Put_Image and for the aspects corresponding to the stream-oriented attributes. [Tucker Taft replied:] As far as null extensions, I would agree with Steve we should suppress them. So I would suggest: Modify 4.10(6/5): For an untagged derived type{, or a null extension}, the default implementation of T'Put_Image invokes the Put_Image for its parent type on a conversion of the parameter of type T to the parent type. Modify 4.10(15/5): For a {nonnull} type extension, the default implementation of T'Put_Image depends on whether there exists a noninterface ancestor of T (other than T itself) for which the Put_Image aspect has been directly specified. If so, then T'Put_Image will generate an image based on extension aggregate syntax where the ancestor type of the extension aggregate is the nearest ancestor type whose Put_Image aspect has been specified. If no such ancestor exists, then the default implementation of T'Put_Image is the same as described below for a nonderived record type. --- As far as the subtype of the parameters, for stream attributes we have: 13.13.2(50/3-51/3): In the parameter_and_result_profiles for the default implementations of the stream-oriented attributes, the subtype of the Item parameter is the base subtype of T if T is a scalar type, and the first subtype otherwise. The same rule applies to the result of the Input attribute. For an attribute_definition_clause specifying one of these attributes, the subtype of the Item parameter shall be the first subtype or the base subtype if scalar, and the first subtype if not scalar. The same rule applies to the result of the Input function. We presumably need something analogous for Put_Image. As far as the use of "abstract" procedures for Put_Image, I agree we want to disallow them. All types have Put_Image, including all abstract types, and we want to be able to presume it is a non-abstract procedure in generics, etc. And the implicitly composed Put_Image for a type extension of an abstract type will invoke the parent's operation, so again, we want to be sure it is nonabstract. Hence, I recommend we add a paragraph to specify both the parameter subtype, and the requirement to be nonabstract: Add after 4.10(26/5): In the parameter_and_result_profile for the default implementation of Put_Image, the subtype of the Arg parameter is the base subtype of T if T is a scalar type, and the first subtype otherwise. For an aspect_specification or attribute_definition_clause specifying Put_Image, the subprogram name shall denote a nonabstract procedure whose second parameter is either of the first subtype of T, or as an option when T is scalar, the base subtype of T. --- As far as "implicitly composed" we do say something in 4.10(4/5) about Put_Image, but there is nothing about how the stream attributes are implicitly composed. So I would suggest: Modify 13.13.2(9.1/5): {For derived types, the default implementations of the Write and Read attributes are not inherited, but are rather implicitly composed.} For type extensions, the Write or Read attribute for the parent type is called, followed by the Write or Read attribute of each component of the extension part, in canonical order. For a limited type extension, if the attribute of the parent type or any progenitor type of T is available anywhere within the immediate scope of T, and the attribute of the parent type or the type of any of the extension components is not available at the freezing point of T, then the attribute of T shall be directly specified. For untagged derived types, the Write (resp. Read) attribute invokes the corresponding attribute of the parent type, if the attribute is available for the parent type. **************************************************************** From: Tucker Taft Sent: Sunday, May 30, 2021 9:00 AM Randy recently posted AIs for our upcoming review of WG9 comments. He asked me to comment on the Put_Image/Stream attribute fixups. Here are two of the four issues for which I had a comment: >(1) Modify 4.10(6/5): > For an untagged derived type{, or a null extension}, the default > implementation of T'Put_Image invokes the Put_Image for its parent type > on a conversion of the parameter of type T to the parent type. >Modify 4.10(15/5): > For a {nonnull} type extension, the default implementation of T'Put_Image > depends on whether there exists a noninterface ancestor of T (other than T > itself) for which the Put_Image aspect has been directly specified. If so, > then T'Put_Image will generate an image based on extension aggregate syntax > where the ancestor type of the extension aggregate is the nearest ancestor > type whose Put_Image aspect has been specified. If no such ancestor exists, > then the default implementation of T'Put_Image is the same as described > below for a nonderived record type. >... >(4) Modify 13.13.2(9.1/5): > {For derived types, the default implementations of the Write and Read > attributes are not inherited, but are rather implicitly composed.} For > type extensions, the Write or Read attribute for the parent type is > called, followed by the Write or Read attribute of each component of the > extension part, in canonical order. For a limited type extension, if the > attribute of the parent type or any progenitor type of T is available > anywhere within the immediate scope of T, and the attribute of the parent > type or the type of any of the extension components is not available at > the freezing point of T, then the attribute of T shall be directly > specified. For untagged derived types, the Write (resp. Read) attribute > invokes the corresponding attribute of the parent type, if the attribute > is available for the parent type. >[Editor's complaint: This only covers Read and Write. What about Input >and Output, and the class-wide versions of all of these?? I would have expected >a general statement at the top of 13.13.2 instead of this.] I think we could instead put a general statement at the end of paragraph 13.13.2(38/4): Modify 13.13.2(38/4): The stream-oriented attributes may be specified for any type via an attribute_definition_clause. [Alternatively, each of the specific stream-oriented attributes may be specified using an aspect_specification on any type_declaration, with the aspect name being the corresponding attribute name.] Each of the class-wide stream-oriented attributes may be specified using an aspect_specification for a tagged type T using the name of the stream-oriented attribute followed by 'Class; such class-wide aspects do not apply to other descendants of T. {If not directly specified, a default implementation of a stream-oriented attribute is implicitly composed for a nonlimited type, and for certain limited types, as defined above.} >!discussion >(1) It's unclear to the Editor whether this is really a principle that we >want to support. OTOH, he has consistently objected to treating null >extensions specially and has lost that argument consistently. As far as the proposal about null extensions identified as issue (1) above, here is the logic: If you have a tagged type T1 and its null extension T2, the default implementation of the Put_Image attributes for T1 and T2 will be identical, since they will each be displayed as a record aggregate, and they have the same number of components. Similarly, if the parent of T1 (or some more remote ancestor) has a Put_Image attribute specified (let's call it T0), then the implicitly composed Put_Image attributes for T1 and T2 will again be identical, since they have the same set of additional components relative to T0. So it makes sense that in general the implicitly composed Put_Image attribute for T2 should be the same as whatever T1'Put_Image produces. Another reason for null extensions to directly reuse the Put_Image of their parent is that a standard idiom for making a tagged type exported by a generic instantiation directly visible, is by using a null extension, so it would be annoying to instantiate a generic which provides a Put_Image, and then suddenly find a "(... with Null)" wrapper appearing around all of the 'Image results. Finally, we have gone out of our way to make (essentially) all primitive operations of a type be inherited "as is" by a null extension, including functions with controlling results, which would normally require overriding. So "inheriting" Put_Image (via implicit composition) as is, makes sense as well. ... **************************************************************** From: Randy Brukardt Sent: Wednesday, June 2, 2021 9:43 PM >> !discussion >> (1) It's unclear to the Editor whether this is really a principle that we >> want to support. OTOH, he has consistently objected to treating null >> extensions specially and has lost that argument consistently. > As far as the proposal about null extensions identified as issue (1) above, > here is the logic: If you have a tagged type T1 and its null extension T2, the > default implementation of the Put_Image attributes for T1 and T2 will be > identical, since they will each be displayed as a record aggregate, and they > have the same number of components. Similarly, if the parent of T1 (or some > more remote ancestor) has a Put_Image attribute specified (let's call it T0), > then the implicitly composed Put_Image attributes for T1 and T2 will again be > identical, since they have the same set of additional components relative to > T0. So it makes sense that in general the implicitly composed Put_Image > attribute for T2 should be the same as whatever T1'Put_Image produces. This seems like justifying a glitch by doubling down on the glitch: I see no reason that T1 and T2 necessarily have the same Image, by default or any other reason. Perhaps the advice makes that the case, but that would seem to be accidental rather than any sort of intentional result. > Another reason for null extensions to directly reuse the Put_Image of their > parent is that a standard idiom for making a tagged type exported by a > generic instantiation directly visible, is by using a null extension, so > it would be annoying to instantiate a generic which provides a Put_Image, > and then suddenly find a "(... with Null)" wrapper appearing around all of > the 'Image results. This "idiom" is evil, and needs to be replaced by something (anything!) else. One *never* wants to introduce a type unless one really needs it to be different in an apples-and-oranges sense. If the Image was to increase encouragement to avoid this idiom (and perhaps provide a replacement, as in deferred AI12-0229-1), that would seem to be a good thing! > Finally, we have gone out of our way to make (essentially) all primitive > operations of a type be inherited "as is" by a null extension, including > functions with controlling results, which would normally require > overriding. So "inheriting" Put_Image (via implicit composition) as is, > makes sense as well. This is also evil, at least for the likely uses that I have for null extensions. Most likely, a null extension is just a placeholder for a type that eventually will gain extension components (it's unlikely that one can usefully replace operations of a type without some additional/different data). In this case, getting a clean compiler for a null extension tells one nothing about the requirements on the type in the actual program; one has to declare dummy components in order to do that. Which brings up all of the problems of dummy components. I would not object to extending this capability to other extensions (those with extension components with appropriate defaults), as that would mitigate the problem -- but of course no extension to Image would make sense in that case. (AI12-0083-1, also deferred, proposed something on this line to make classic mix-in generics more general.) Anyway, as I stated at the outset, I've consistently lost this argument, so I'll go crawl back under my rock. :-) **************************************************************** From: Richard Wai Sent: Wednesday, June 2, 2021 11:10 PM Since I pretty strongly agree with what Tuck said generally, I will just chime in to say that, from a higher level engineering or design perspective, you can (and I often will) make the case for declaring a new type that is exactly the "same" as an existing type. If your real purpose in the use of a type is to assert that some data is different than some other data with the exact same representation, then you'd want separate types. Of course we all know the classic example of measurement units and the like, but I don't see why this couldn't also apply to (tagged) record types. As a crude example, say I have: type Contact_Information record record Name: Name_String; Address: Address_String; Email: Email_String; end record; procedure Send_Email (Contact: in Contact_Information; Content: in String); In my application, I store contact information for businesses and individuals. I can know, as a matter of design, that I need to treat businesses and individuals very differently, but I may need to do various actions on their contact info, such as sending an automated email, which might be essentially the same between businesses and individuals. In this case, it makes perfect sense to define both type Individual_Contact and type Business_Contact as null extensions of Contact_Information. That way I can be sure that nothing that expects a business contact will get a individual contact (unless explicitly converted). I'd of course not get that same protection if I was merely using subtypes. Additionally, I might (or might not) want to make selective overrides to the operations of the type, while inheriting the rest (such as Put_Image). So I'd disagree with your comment that "it's unlikely that one can usefully replace operations of a type without some additional/different data)" . In this example, merely knowing that the contact is for a business rather than a client determines how I will interpret or use the components of the record. Of I do agree with you that the generic instantiation idiom is probably evil! **************************************************************** From: Tucker Taft Sent: Thursday, June 3, 2021 7:40 AM >> !discussion >> (1) It's unclear to the Editor whether this is really a principle >> that we want to support. OTOH, he has consistently objected to >> treating null extensions specially and has lost that argument >> consistently. > As far as the proposal about null extensions identified as issue (1) > above, here is the logic: ... >Anyway, as I stated at the outset, I've consistently lost this argument, so >I'll go crawl back under my rock. :-) In most cases I find that I understand your arguments when you feel strongly about something, but I will admit I do not understand your strong objection to the use of "null extensions" as the way to create a new tagged type that is "nearly the same" as some existing type (particularly a type produced as a result of a generic instantiation). Of course if you don't like them, you don't have to use them, but I have many examples from my own experience where it seems to be the right thing to do, and does not seem to be "evil" from a software engineering point of view. So presuming other folks might have similar experiences with how it is appropriate to use null extensions, we should have a consistent set of language principles about null extensions, and I believe the proposed fix provides that consistency. **************************************************************** From: Randy Brukardt Sent: Thursday, June 3, 2021 11:22 PM >In most cases I find that I understand your arguments when you feel strongly >about something, but I will admit I do not understand your strong objection >to the use of "null extensions" as the way to create a new tagged type that >is "nearly the same" as some existing type (particularly a type produced as >a result of a generic instantiation). Of course if you don't like them, you >don't have to use them, but I have many examples from my own experience >where it seems to be the right thing to do, and does not seem to be "evil" >from a software engineering point of view. It not about not liking "null extensions"; of course one has to use them often. The problem is that this special semantics substantially interferes with the sort of incremental development that I (strongly) prefer. I explained this in more detail in one of my responses to AI12-0083-1, so let me steal from that: This is my problem with the existing null extension rule; future maintenance often adds extension components, and when that happens, all of a sudden you have to write a bunch more routines. That's especially annoying given the way that I typically create extensions (write a null extension, override all of the routines that will need overriding, with TBD bodies [raise Program_Error or the like], compile to make sure everything is correct, then start implementing the routines -- at which point extension components get added and all of the work to check that everything is overridden has to thrown out the window and redone). "All of the routines that will need overriding" include any abstract ancestors, as well as routines that "obviously" will need new versions. This step is very time-consuming for a complex set of types (it takes a day or more for the Claw Builder's types), and rather violates the principles of incremental development that I've always used (long before that was a "thing"). (That is, always have a testable version for every few hours of work.) Having to redo it halfway through development is very frustrating, especially as I tend to use a lot of functions returning a tagged type. If there was a way to let a regular extension use this same machinery (as suggested in AI12-0083-1), this objection would be mitigated quite a bit, as most extension components have reasonable defaults that could be used. It wouldn't always work, but that's OK (can't expect the compiler to write your program for you in every instance!) >So presuming other folks might have similar experiences with how it is >appropriate to use null extensions, we should have a consistent set of >language principles about null extensions, and I believe the proposed fix >provides that consistency. The problem isn't about using null extensions, the problem is that one can't easily change them into a regular extension when that (inevitably in my experience) needs to occur. Doing so breaks a substantial amount of the code already written, and forces overriding a substantial percentage of the routines. I rarely see a type that has no extension components, since it is unlikely that one can write different operations using only the existing components. It can happen, but not very often. I realize that others use these things differently, specifically finding some value in interface inheritance, so they might see null extensions more frequently, but it still seems rare to me. (Introducing otherwise unneeded types just for visibility purposes will always be evil to me, however; either use a "use all" for such a thing or lobby for a proper language fix and write out the longer names until then.) Anyway, as I said previously, I have zero expectation of winning this argument -- ever -- but I detest the difference between null extensions and other extensions (and the mix-in problems raised in AI12-0083-1 illustrate the issues as well). **************************************************************** From: Tucker Taft Sent: Friday, June 4, 2021 7:10 AM Clearly the narrow issue of Put_Image is now overtaken by events. But in the larger issue, I have no argument with giving more extensions the advantages of null extensions when it comes to automatically providing implementations of functions with controlling results. But at this point we clearly aren't going to *remove* the advantages that null extensions provide, and even if we give more extensions the advantages of null extensions, the proposed (now approved) change to Put_Image would still make sense in my view. As far as the incremental development issue, I would suggest you use a private extension rather than a visible null extension in your initial extension, if you are having the trouble you describe. **************************************************************** From: Bob Duff Sent: Friday, June 4, 2021 8:22 AM > As far as the incremental development issue, I would suggest you use a > private extension rather than a visible null extension in your initial > extension, if you are having the trouble you describe. Doesn't Randy also need to put in a dummy component ("TBD: Boolean := raise Program_Error;" or some such)? In any case, he can't fully write the required overridings until he's decided what components he wants to have. **************************************************************** From: Randy Brukardt Sent: Friday, June 4, 2021 10:21 PM ... > Doesn't Randy also need to put in a dummy component ("TBD: > Boolean := raise Program_Error;" or some such)? > > In any case, he can't fully write the required overridings until he's > decided what components he wants to have. True enough, but my primary goal is always to keep the code compilable as much as possible. That allows detecting errors in code I just wrote before I forget the details of what it is supposed to do. The sorts of unfinished sentences that turn up in AIs periodically appear in my code, too, and finishing them requires having an idea of what they are supposed to be. It's almost always the case that I have to add additional overridings after the initial bunch, but those don't interfere with compilation like the initial requirement to override abstract routines (and functions if it isn't a null exclusion) does. In a system like the Claw Builder, I typically have a root abstract type that everything else derives from, and that type provides default implementations of some routines where that makes sense -- but the others are abstract. This is admittedly a problem where every solution is lousy -- one could avoid the compilation issues by having useless default versions, but then the possibility of forgetting to implement something critical would probably cause as much trouble. One gets similar problems using variants and case statements in Ada, as the completeness checks provide the same benefits and costs. I just hate having to do it twice because of the null extension differences. ****************************************************************