!standard 4.3.4(14/5) 20-06-10 AI12-0381-1/02 !class Amendment 20-06-09 !status work item 20-06-09 !status received 20-04-19 !priority Low !difficulty Easy !subject Tag of a delta aggregate !summary The tag of a delta aggregate of a specific tagged type is that of the type. !problem Consider: type Some_Tagged is tagged record L : Natural; Data : array (1..10) of Some_Controlled_Type; end record; procedure Do_Something (A : Some_Tagged) is My_Obj : Some_Tagged := (A with delta L => 10); -- (1) begin ... end Do_Something; type An_Ext is new Some_Tagged with record Z : Some_Controlled_Type; end record; Obj : An_Ext := ...; Do_Something (Some_Tagged (Obj)); Some_Tagged is required to be built-in-place by 7.6(17.3/3). The tag of the aggregate (1) is that of An_Ext by 4.3.4(14/5), as the base expression has that tag (type conversions do not change the tag of tagged objects). The base expression is used to initialize My_Obj, which must include copying and adjusting the extension component Z. But there is no place in My_Obj for Z -- My_Obj does not have that extension. If the array of Some_Tagged had been of some non-controlled type (say Natural), then we would still be required to build the aggregate in place (as there still is a controlled component in the extension), but the compiler doesn't even know that at compile-time for the aggregate at (1). Clearly something is wrong here. !proposal We propose to eliminate the rule that the tag of the new object comes from the base object, replacing that with the type of the aggregate. We also adjust the wording so it is clear that the aggregate is initialized with the part of the base_expression that corresponds to that tag. Most other cases in Ada discard the tag of the original value; even a function return discards the tag of the returned expression, so it seems confusing that a delta aggregate of a specific type would try to preserve the underlying tag of the base object. If that is the desired semantics, the base expression can be converted to a class-wide type, to make that intent very clear. !wording Modify 4.3.4(14/5): The evaluation of a delta_aggregate begins with the evaluation of the base_expression of the delta_aggregate; then that value is used to create and initialize the anonymous object of the aggregate. The bounds of the anonymous object of an array_delta_aggregate and the discriminants [and tag] (if any) of the anonymous object of a record_delta_aggregate are those of the base_expression. {If a record_delta_aggregate is of a specific tagged type, its tag is that of the specific type; if it is of a class-wide type, its tag is that of the base object.} Replace 4.3.4(14.b/5) [part of an AARM Rationale] with: For a specific tagged type, any extension components that the actual object underlying the base_expression might have are not used to initialize the anonymous object, which is critical if the aggregate is required to be built-in-place. !discussion The alternative to the proposed solution would be some sort of modification to the build-in-place rules that would avoid copying (unknown) extension components. This would mean that the semantics would be subtly different when built-in-place is used, as some Adjust/Finalize pairs that otherwise would be called would not be called. This seems unwise, especially as implementations are allowed but not required to use build-in-place in many additional cases. Moreover, the original requirement to use the tag of the base object would mean that for the case when build-in-place is not used, the size of the aggregate would depend on the base object. For many cases, this is not known at compile-time. This means that shifting from an untagged record to a tagged record will require switching from a static to dynamic allocation of temporary space with the resulting increase in implementation complexity and expense. It might also be necessary to adjust the result even though no controlled components are visible. There is no other operation in the language that operates this way other than 'Old, and 'Old is only allowed within postconditions, which are not considered performance sensitive. Furthermore, 'Old preserves everything, whereas a delta aggregate only allows specifying changes to the visible components, with no way to indicate a change in the parts that are in whatever unknown extension the object might have. This means that if the base object were passed via a redispatch to some operation that could see the extension, it must not actually make any change to the extension, and only would be allowed to change the part visible originally. This seems to be a real corner case, and not something justifying this added expense, and quite possibly misleading in cases where in some overridings of the operation parts of the extension *are* updated, and in others not. Note also that any assignment or comparison against the delta aggregate would ignore the tag and the extension parts, which makes all the work seem doubly wasteful. We conclude that the base expression should be "truncated" to the type of the aggregate, if that type is a specific tagged type. Note that we do allow delta aggregates to have class-wide tagged types. This provides all of the capability that might be lost by changing the specific-type delta aggregate, and makes the semantics very clear and explicit. !examples X : T := (Y with delta A => 3, B => 4); -- Size and tag of aggregate are determined by T. Above delta aggregate is roughly equivalent to (ignoring the liberties with the syntax): Temp : constant T := Y; Temp.A := 3; Temp.B := 4; X : T := Temp; If the build-in-place permission is used, the Temp can be eliminated, giving: X : T := Temp; X.A := 3; X.B := 4; !ASIS No ASIS effect. !ACATS test An ACATS C-Test is needed to check that the new rules are enforced, rather than the previous rules. The test program given below can be the basis of an ACATS C-Test. !appendix From: Tucker Taft Sent: Sunday, April 19, 2020 7:07 AM I just bumped into a case where I really wanted to change an access discriminant of a record object, and nothing else. So I immediately thought how nice it would be to have delta aggregates. And then to my dismay realized that we might not allow this use case, if we have assignment semantics of (record) delta aggregates. So I looked at the proposed wording in 4.3.4 and sure enough we use (re)assignment rather than initialization semantics for record delta aggregate. I realize that for array delta aggregates we get some value out of using (re)assignment semantics, but I am not convinced that we do for record delta aggregates, and we clearly lose the ability to substitute a new discriminant value. Here is the existing Dynamic Semantics wording from 4.3.4: 14/5 {AI12-0127-1} {AI12-0324-1} The evaluation of a delta_aggregate begins with the evaluation of the base_expression of the delta_aggregate; then that value is used to create and initialize the anonymous object of the aggregate. The bounds of the anonymous object of an array_delta_aggregate and the discriminants and tag (if any) of the anonymous object of a record_delta_aggregate are those of the base_expression. 14.a/5 Ramification: This is the same anonymous object as described in 7.6, “Assignment and Finalization”; in particular, it might be required to be built in place. 14.b/5 This requires that the underlying tag (if any) associated with the delta_aggregate is that of the base_expression in the delta_aggregate and not that of the nominal type of the base_expression in the delta_aggregate. 14.c/5 To be honest: The anonymous object associated with the evaluation of a delta_aggregate begins its life as a variable, not a constant (3.3 notwithstanding). This must be the case because the object is initialized and then subsequently modified. After evaluation of the delta_aggregate is complete, the object is a constant object. This is similar to the way that an extended return statement can provide a variable view of an object that will eventually be a constant object after the function returns its result. 15/5 {AI12-0127-1} For a record_delta_aggregate, for each component associated with each record_component_association (in an unspecified order): 16/5 * if the associated component belongs to a variant, a check is made that the values of the discriminants are such that the anonymous object has this component. The exception Constraint_Error is raised if this check fails. 17/5 * the expression of the record_component_association is evaluated, converted to the nominal subtype of the associated component, and assigned to the component of the anonymous object. ---- I would propose that we split the dynamic semantics for record delta aggregates and array delta aggregates completely. We already did that for the Name Resolution and Legality Rules. For record delta aggregates, I think we could make the semantics be more like creating a new aggregate, rather than like copying (with adjust, presumably) and then assigning components. The wording in 4.3.1 in the Name Resolution and Legality Rules sections would have to allow discriminants to be named in a record delta aggregate component_selector_name. A separate Legality Rule would disallow naming a discriminant that governs a variant part of the record type. The dynamic semantics of 4.3.4 would end up something like this: The evaluation of a record_delta_aggregate begins with the evaluation of the base_expression of the record_delta_aggregate. Then, for each component associated with each record_component_association: * if the associated component belongs to a variant, a check is made that the values of the discriminants of the base object are such that the anonymous object has this component. The exception Constraint_Error is raised if this check fails. * the expression of the record_component_association is evaluated, converted to the nominal subtype of the associated component, and defines the value of the associated component of the anonymous object; if the associated component is a discriminant, any per-object constraint that depends on the discriminant is then elaborated. The above evaluations (and conversions) happen in an arbitrary order, except that the evaluation and conversion of the expression for a component that depends on a discriminant specified in the record_component_association_list happens after the elaboration of any per-object constraint that depends on that discriminant. The value of any component of the anonymous object that is not associated with one of the record_component_associations is defined by the corresponding component of the result of evaluating the base_expression. --- Comments? **************************************************************** From: Richard Wai Sent: Sunday, April 19, 2020 7:20 PM > I just bumped into a case where I really wanted to change an access > discriminant of a record object, and nothing else. So I immediately > thought how nice it would be to have delta aggregates. And then to my > dismay realized that we might not allow this use case, if we have > assignment semantics of (record) delta aggregates. So I looked at the > proposed wording in 4.3.4 and sure enough we use (re)assignment rather > than initialization semantics for record delta aggregate. This seems like an extremely useful feature which would definitely be a natural application of record delta aggregates! > I realize that for array delta aggregates we get some value out of > using (re)assignment semantics, but I am not convinced that we do for > record delta aggregates, and we clearly lose the ability to substitute > a new discriminant value. This makes sense. Particularly it seems counter-intuitive that the base_expression is responsible for "initializing" the anonymous object since a delta aggregate is an obvious way to generate a new record based on an existing "progenitor" / "template". In my experience, this is not unusual at all, and currently entails a needlessly verbose aggregate with a lot of (A => X.A, B => X.B), and isn't that the whole point of having delta aggregates in the first place? In other words, initialization of new object via a delta aggregate would probably be common, and in many of those cases you'd naturally want to be setting the discriminants as well.. And if it was legal to do that with a regular aggregate, it shouldn't be illegal for a delta aggregate! > ---- > > I would propose that we split the dynamic semantics for record delta > aggregates and array delta aggregates completely. We already did that > for the Name Resolution and Legality Rules. For record delta > aggregates, I think we could make the semantics be more like creating > a new aggregate, rather than like copying (with adjust, presumably) and then assigning components. This definitely is nice an orthogonal, and I can't imagine any good argument against it (though with my limited ARG background, that probably doesn't say much). > The dynamic semantics of 4.3.4 would end up something like this: > But in many these semantics seem even clearer than the original (to me). > The above evaluations (and conversions) happen in an arbitrary order, > except that the evaluation and conversion of the expression for a > component that depends on a discriminant specified in the > record_component_association_list happens after the elaboration of any > per-object constraint that depends on that discriminant. > I suppose this might be the tricky part.. > --- > > Comments? Strong support from my end. **************************************************************** From: Tucker Taft Sent: Sunday, April 19, 2020 7:56 PM Thanks, Richard. Glad it seems useful to you. I realize that one fix needed is to revise the final sentence as follows: The value of any component of the anonymous object that is not associated with one of the record_component_associations is defined by the corresponding component of the result of evaluating the base_expression{, converted to the subtype of the component as determined by the new discriminant values}. This conversion is really just a constraint check, though it could be doing sliding if both the low and high bounds were controlled by updated discriminants. Normally you would only update a discriminant if you intended to update the discriminant-dependent components as well. **************************************************************** From: Tucker Taft Sent: Sunday, April 19, 2020 8:29 PM I suppose we could go one step further, and allow updating discriminants that govern a discriminant part, so long as all of the newly "needed" components are provided in the record_component_associations of the delta aggregate. I thought perhaps that was not particularly useful, but in fact it might be if the record has a lot of components in the non-variant part, and only a few components in the variant part. Once you see the base_expression as a kind of "backup" that provides the needed but unspecified components, changing a discriminant is not that big of a deal. **************************************************************** From: Jean-Pierre Rosen Sent: Monday, April 20, 2020 3:27 AM >> I realize that for array delta aggregates we get some value out of >> using (re)assignment semantics, but I am not convinced that we do for >> record delta aggregates, and we clearly lose the ability to >> substitute a new discriminant value. > This makes sense. Particularly it seems counter-intuitive that the > base_expression is responsible for "initializing" the anonymous object > since a delta aggregate is an obvious way to generate a new record > based on an existing "progenitor" / "template". In my experience, this > is not unusual at all, and currently entails a needlessly verbose > aggregate with a lot of (A => X.A, B => X.B), and isn't that the whole > point of having delta aggregates in the first place? Which makes me think (admitedly without studying all the implications), why don't we simply use the model of default values of subprograms, rather than that bunch of complicated rules? i.e., in (X with delta ...) every needed component (including discriminants) of the aggregate not explicitely provided is added by the compiler as C => X.C, and then let normal aggregates rules work. Might be slightly more difficult for array aggregates though. **************************************************************** From: Tucker Taft Sent: Monday, April 20, 2020 6:34 AM Array delta aggregates are intentionally different, because they allow multiple dynamic replacements, which might overlap, so it is defined to be an ordered sequence. Record delta aggregates are unordered; positional notation is in fact not permitted. I agree that the model of defaults is a nice one, but as usual with wording, the devil is in the details. **************************************************************** From: Randy Brukardt Sent: Monday, April 20, 2020 1:04 PM That model only works when the set of components is known at compile-time. For a delta aggregate that includes no discriminants when the base expression is unconstrained and (especially) has variants, there's no way to figure out the components needed at compile-time. And that would require a completely new mechanism unlike anything currently in Ada. Given that the initial use case for delta aggregates is exactly this sort of record type, that seems very bad. **************************************************************** From: Gary Dismukes Sent: Monday, April 20, 2020 1:21 PM > ... > ---- > > I would propose that we split the dynamic semantics for record delta > aggregates and array delta aggregates completely. We already did that > for the Name Resolution and Legality Rules. For record delta aggregates, > I think we could make the semantics be more like creating a new aggregate, > rather than like copying (with adjust, presumably) and then assigning > components. > > The wording in 4.3.1 in the Name Resolution and Legality Rules sections > would have to allow discriminants to be named in a record delta aggregate > component_selector_name. A separate Legality Rule would disallow naming a > discriminant that governs a variant part of the record type. > > The dynamic semantics of 4.3.4 would end up something like this: > > ... > > Comments? Sounds to me like a useful and relatively straightforward change. **************************************************************** From: Randy Brukardt Sent: Monday, April 20, 2020 1:26 PM I'm opposed to this change for two reasons. First, I think that allowing general delta aggregates essentially eliminates much of the value of aggregates in the first place, which is the requirement that all components be covered. By definition, delta aggregates do not do that. Having such aggregates that change a component or two doesn't really harm that value much. But once we start allowing completely arbitrary changes, that protection is completely gone. Anyone could skip the completeness check by using a delta aggregate on a dummy object. One of the reasons that I use Ada is because it protects me from my worst instincts. I'm not happy with giving me another way to end-run protection, because I *know* I'll end up using it. I realize that Tucker proposed not allowing variant changes in delta aggregates. That essentially means that I personally could never use the enhanced possibilities (the only discriminants I use control variants). So this would be a lot of work for me with no personal benefit. And this is a massive amount of work for Janus/Ada. The existing semantics of delta aggregates is described in terms of operations that already exist (initializing an object from another object, and writing an aggregate component into an object). However, if any discriminant controlling a component can be changed by an aggregate, then that is no longer true. That's because initializing a temporary from the base object would no longer work -- it would allocate possibly the wrong size for discriminant-dependent components. The only way to implemnt this would be to copy the discriminants from be base object into the top-level temporary (and a temporary would be required somewhere), set the discriminants from the aggregate, allocate the memory for the temporary object, and then copy the other components either from the aggregate or the base object individually. That would be 100% new code (there's nothing like that anywhere in Ada now). It could be done without any massive changes sewhere only if variants are not allowed (as Tucker had proposed), but it would be a lot or work for very little gain. I also note that this would introduce a number of new cases for coextensions (that is, a useless concept layered on top of a rarely used concept ;-). In particular, you could change whether each the discriminants individually is a coextension. (One could be changed and one could be left the same; currently, you can only change them all at once.) Not sure if this would make things harder (this is a feature not close to worth the work, so I haven't analyzed it very carefully). Anyway, I don't think any of these issues is likely to convince many others, so if the rest of you want to go ahead, don't let me stop you. But don't expect any support from me, either. **************************************************************** From: Steve Baird Sent: Monday, April 20, 2020 1:48 PM > i.e., in (X with delta ...) > every needed component (including discriminants) of the aggregate not > explicitely provided is added by the compiler as C => X.C, and then > let normal aggregates rules work. Randy pointed out one case where the set of needed components cannot be identified statically. Here is another one, based on the fact that a delta aggregate (X with delta ...) preserves the underlying tag of X in the case where X is tagged. IIUC, the proposed rule would not properly initialize the F3 component of the delta aggregate in this example: procedure Delta_Agg is type T1 is tagged record F1, F2 : Integer; end record; type T2 is new T1 with record F3 : Integer; end record; procedure P (X : T1) is Y : T1'Class := (X with delta F2 => 444); begin pragma Assert (Y = T1'Class (T2'(111, 444, 333))); end P; begin P (T1 (T2'(111, 222, 333))); end Delta_Agg; I'm not saying this couldn't be fixed, but a fix of some sort would be needed. **************************************************************** From: Randy Brukardt Sent: Monday, April 20, 2020 4:04 PM Thanks, Steve. You've pointed out that since one can have a delta aggregate for a class-wide type (or a specific type where the actual object is of some descendant), you don't know at compile-time even what components *might* need to be copied. (For an untagged type, you at least know the components that might be involved, although with no idea which ones exactly are involved.) So that means that one would have to, at a minimum, have different implementations for tagged and untagged types (note that one can derived a mutable untagged record from an immutable untagged record, so that in general one cannot assume anything because the type you are holding is immutable, something I ran into in Janus/Ada debugging over the weekend). A tagged delta aggregate cannot change any discriminants, so including them would be noise (which would have to be checked at runtime as well). And the very messy implementation I outlined before would be needed otherwise. Moreover, a component-at-a-time model like described by J-P would be nearly impossible to implement for a tagged object, as the underlying object could be any extension of it, and copying a bunch of unknown components seems essentially impossible. One couldn't reasonably use a dispatching operation to do that, since one would need such an operation for every combination of types and specified components possible (which is obviously impractical). Even one such operation for every ancestor type seems impractical. I conclude that a "component-at-a-time" model doesn't work,, both from a language definition point of view, and from an implementation point of view. The model Tucker suggested sort of works, but it requires changing the "shape" of temporary objects after they're created, which is a new requirement on Ada implementations. That also seems like a bad idea to me. **************************************************************** From: Tucker Taft Sent: Monday, April 20, 2020 7:26 PM OK, thanks for all the useful feedback. Let me try to write up something that steers clear of these icebergs, but still provides some useful value. I think it is possible, but we'll see! If all else fails we fall back to the existing capability, which is still useful. But I think it will be frustrating in some cases when a "simple" discriminant update is desired. But I can see that going for full generality has lots of problems. So my intent is to find an intermediate position that supports the common cases, while avoiding the nasty corners. **************************************************************** From: Randy Brukardt Sent: Monday, April 20, 2020 7:47 PM I think you *might* be OK if you restrict yourself to discriminants that don't discriminate anything (that is, have no discriminant-dependent components), and probably require all or nothing on access discriminants (not as sure about this). Your original case of changing an access discriminant is usually in that category. **************************************************************** From: Jean-Pierre Rosen Sent: Tuesday, April 21, 2020 2:29 AM > First, I think that allowing general delta aggregates essentially > eliminates much of the value of aggregates in the first place, which > is the requirement that all components be covered. By definition, > delta aggregates do not do that. Having such aggregates that change a > component or two doesn't really harm that value much. But once we > start allowing completely arbitrary changes, that protection is > completely gone. Anyone could skip the completeness check by using a > delta aggregate on a dummy object. I must say I have some sympathy with Randy's position. TBH, I am worried that the trend of evolution is for ease of writing more than ease of reading or maintaining. That being said, if the "default value" model does not work, it seems to me that a delta aggregate: (Rec with delta A=> Val_A, B => Val_B) should be equivalent to: declare Temp : Rec_T := Rec; begin Temp.A := Val_A; Temp.B := Val_B; -- use Temp... end; Of course, this model may imply more adjust/finalize than the current proposal if there are controlled components. It also shows that delta aggregates are, at best, a convenience. OTOH, I understand that nowadays, ease of writing makes more for popularity of languages than ease of maintenance. That's another one of my concerns... **************************************************************** From: Randy Brukardt Sent: Tuesday, April 21, 2020 5:35 PM > That being said, if the "default value" model does not work, it seems > to me that a delta aggregate: > (Rec with delta A=> Val_A, B => Val_B) > > should be equivalent to: > declare > Temp : Rec_T := Rec; > begin > Temp.A := Val_A; > Temp.B := Val_B; > -- use Temp... > end; This clearly does not allow changing any discriminants (assiging a discriminant individually is never allowed and would be nonsense if there are any discriminant-dependent components). If we stick with that, we have an adequate model for delta aggregates. (Which I believe is essentially the above.) The problems come if we want to allow changing discriminants as Tucker proposed. **************************************************************** From: Richard Wai Sent: Wednesday, April 22, 2020 9:26 PM > I must say I have some sympathy with Randy's position. TBH, I am > worried that the trend of evolution is for ease of writing more than > ease of reading or maintaining. > OTOH, I understand that nowadays, ease of writing makes more for > popularity of languages than ease of maintenance. That's another one > of my concerns... > I strongly agree with you philosophically (a moment of silence of '@').. Though it seems the initial motive behind delta aggregates was to heavily weighted towards making post conditions more concise, and that remains very valuable from a readability standpoint. I suppose it has grown into a whole other beast when you start talking about more general use cases. To me, Tucker's proposal adds some general elegance and regularity (from a user perspective) to record delta aggregates - a sort of universal way to dynamically template a new record based on an existing one, without a too many fussy illegalities. That could improve readability, particularly in object initializations. Though clearly it's easier said than done, and I wonder if it will be worth it once we work all of the kinks out. Ultimately I do think that... type R (Ref: access Integer) is record X,Y,Z: Integer; end record; A: R := (...); B: R := (A with delta Ref => I'Access); ... is more readable than... B: R := (Ref => I'Access, X => A.X, Y => A.Y, Z => A.Z); .. but I suppose your mileage may vary. I'm sure a codebase somewhere will inevitably end up with something like: B: R := (A with delta Ref => A.Ref, X => A.X, Y => A.Y); **************************************************************** From: Tucker Taft Sent: Monday, April 27, 2020 8:57 PM Thanks for the support. But unfortunately, or perhaps fortunately, Randy and Steve together found some "killer" problems in attempting to change any discriminant in a delta aggregate. One of the problems is that you can write a delta aggregate for a type that has some ancestors that are private extensions (and the ultimate ancestor could be a private type). That makes it impossible to know whether a given discriminant is used to constrain the subtype of some hidden component (without breaking privacy, that is). I had forgotten that even an access discriminant can be used to constrain a component of a record-ish type, meaning that any sort of discriminant can have discriminant-dependent components. It is very awkward to alter a discriminant that has discriminant-dependent components, especially if you don't even know how many there are. So I have abandoned this effort. Along the way I became more concerned that a delta aggregate preserves the tag of the "base" expression, even when it is of a specific type. I believe that is generally not what you want to do, and I know it introduces significant overhead for all delta aggregates of a tagged type. Hopefully we can discuss that issue during the ARG meeting, though our agenda is pretty full! **************************************************************** From: Richard Wai Sent: Monday, April 27, 2020 9:03 PM Indeed I saw that, talk! Unfortunately my email got caught in the spam filter and was actually from sometime last week! **************************************************************** From: Tucker Taft Sent: Friday, April 24, 2020 12:14 PM Working as a very effective tag team, Randy and Steve convinced me that my hope of changing an access discriminant, or heaven forbid other sorts of discriminants, in a delta aggregate, was doomed. The key feature of record delta aggregates that I had forgotten is that they are permitted on types that have an ancestor that is a private type or a private extension. The only requirement is that at least one ancestor is a record or record extension. Hence, there can be many components that are not visible at the point of the delta aggregate. This means that those hidden components might use the discriminant (including an access discriminant) as part of a constraint on the component's subtype. So even an access discriminant can have discriminant-dependent components. Groan. Anyway, so I give up on this idea. However... along the way I came to fully understand the implications of the statement that the tag of the delta aggregate (presuming it is tagged) comes from the tag of the base object, even for an aggregate of a specific type. I think we should reconsider this, and instead say that the tag comes from the type of the aggregate, presuming it is of a specific tagged type. This is for various reasons: 1) Preserving the tag of the base object is significantly more complex, and means the size (and tag) of the delta aggregate is never known at compile-time. You always need to do a dispatching call on the 'Size of the base object (after implicitly converting it to class-wide) to determine the size you need to allocate. This means that switching a type from untagged to tagged completely changes the performance impact of using a delta aggregate, when in fact preserving the tag is rarely of any interest at the place where the delta aggregate is created. 2) The only other place we preserve a tag is X'Old, which is allowed only in the assertion expression of a postcondition. We know that efficiency concerns are generally secondary for postconditions, as they often repeat some of the logic of the associated subprogram just to be able to describe its effects. In all other contexts, when you have a "specific-type" view of an object, the tag is irrelevant. If I write: Y : T := X; the tag of X is irrelevant, presuming T is of a specific type. The only time the tag becomes relevant is if you convert to class-wide in anticipation of re-dispatching. We are providing a class-wide version of the delta aggregate, so you could very easily convert X to T'Class if you wanted its tag to be relevant to the result. 3) Normal use-cases of the delta aggregate, even those involving a base object of X'Old, are not interested in the tag. E.g.: procedure P(X : in out T) with Post => X = (X'Old with A => 3, B => 4); Furthermore, if you do take a delta aggregate, and convert it to class-wide after constructing it, you are effectively claiming that no changes were made to the "extension" of the base object. E.g. with Post => T'Class(X) = T'Class(T'(X'Old with A => 3, B => 4)) is kind of a weird thing to claim, since if we actually did some re-dispatching inside P passing along T'Class(X), how do we know what changes were made to the "extension" of X? If we really *do* know that nothing was done to the extension of X, it would seem much more natural to write: with Post => T'Class(X) = ( T'Class(X'Old) with A => 3, B => 4); where we have a class-wide type for the delta aggregate itself. --- So I believe preserving the tag (along with all of the unknown extension parts) of the base object is most likely not what the programmer wants, and also makes it significantly more expensive to use delta aggregates in "everyday" (non-postcondition) code with tagged types than with untagged types. **************************************************************** From: Randy Brukardt Sent: Friday, April 24, 2020 2:17 PM I can go either way on this one. The implemention doesn't change a lot either way, and the examples I could come up with where it matters mainly seem to demonstrate why redispatching is a bad idea. :-) One point thought: >If we really *do* know that nothing was done to the extension of X, it would >seem much more natural to write: > > with Post => T'Class(X) = ( T'Class(X'Old) with A => 3, B => 4); > >where we have a class-wide type for the delta aggregate itself. It might be "natural", but it's also clearly illegal since the expression of an extension aggregate cannot be dynamically tagged (one has to know the exact extensions involved) - see 4.3.1(5/3). I think the type of the aggregate also has to be specific. The delta aggregate is at least legal and with understandable semantics, which seems like an advantage. ;-) One annoyance here is that the rules would have to be subtly different for classwide delta aggregates and specific tagged delta aggregates. But that's already pretty common in Ada (see function results), and in a lot of cases where there's no specific classwide rule is because classwide isn't allowed at all. So this isn't much of an issue either way. **************************************************************** From: Tucker Taft Sent: Friday, April 24, 2020 2:55 PM >>If we really *do* know that nothing was done to the extension of X, it would >>seem much more natural to write: >> >> with Post => T'Class(X) = ( T'Class(X'Old) with A => 3, B => 4); >> >>where we have a class-wide type for the delta aggregate itself. It might be "natural", but it's also clearly illegal since the expression of an >extension aggregate cannot be dynamically tagged (one has to know the exact >extensions involved) - see 4.3.1(5/3). I think the type of the aggregate also >has to be specific. Oops! Forgot the word "delta." I meant for that to be a (class-wide) record delta aggregate: with Post => T'Class(X) = ( T'Class(X'Old) with delta A => 3, B => 4); >The delta aggregate is at least legal and with understandable semantics, which >seems like an advantage. ;-) Please read the word "delta" in the above example. I forgot "delta" in my other examples as well. I have edited history below by inserting it where it was missing in my e-mail. >One annoyance here is that the rules would have to be subtly different for >classwide delta aggregates and specific tagged delta aggregates. It seems like it only affects the statement about the value of the tag of the result. It might actually simplify wording elsewhere, though not by much either. >But that's already pretty common in Ada (see function results), and in a lot of > cases where there's no specific classwide rule is because classwide isn't > allowed at all. So this isn't much of an issue either way. Agreed. ****************************************************************