!standard 3.3(13) 08-11-26 AI05-0054-2/05 !standard 3.3(25) !standard 13.9.1(13) !class binding interpretation 07-10-24 !status Amendment 201Z 08-11-26 !status ARG Approved 7-0-1 08-11-02 !status work item 07-10-24 !status received 07-10-24 !priority Low !difficulty Hard !qualifier Omission !subject Variable views of constant objects !summary Remove erroneousness associated with using access-to-variable values designating constants of controlled and immutably limited types, so long as they originate at a point where the language provides an aliased variable view of the constant. Revise the definition of constant objects and views to make it clear that there usually is a possibility of a variable view existing. !question The current instance of a limited type can be used to obtain a variable aliased view of a (limited) constant object. Are runtime checks needed in order to prevent the modification of a constant? (No.) !recommendation The practice of obtaining such a variable aliased view during initialization is an existing idiom, and should be allowed to continue even if the object is declared constant. A similar idiom exists for controlled types using Initialize and Adjust rather than the current instance (See the discussion section for more rationale.) !wording Modify 3.3(13) as follows: An object is either a constant object or a variable object. [The value of a constant object cannot be changed between its initialization and its finalization, whereas the value of a variable object can be changed.] Similarly, a view of an object is either a constant or a variable. All views of a constant {elementary }object are constant. {All views of a constant composite object are constant, except for parts that are of controlled or immutably limited types; variable views of those parts and their subcomponents may exist. In this sense, objects of controlled and immutably limited types are inherently mutable.} A constant view of [a variable] {an} object cannot be used to modify [the value of the variable] {its value}. The terms constant and variable by themselves refer to constant and variable views of objects. Add a new NOTE after 3.3(25): The value of a constant object cannot be changed after its initialization, except in some cases where the object has a controlled or immutably limited part (see 7.5, 7.6, and 13.9.1). Modify 13.9.1(13) as follows: The dereference of an access value is erroneous if it does not designate an object of an appropriate type or a subprogram with an appropriate profile, if it designates a nonexistent object, or if it is an access-to-variable value that designates a constant object{ and it did not originate from an attribute_reference applied to an aliased variable view of a controlled or immutably limited object}. [Redundant: [Such an]{An} access value {whose dereference is erroneous} can exist, for example, because of Unchecked_Deallocation, Unchecked_Access, or Unchecked_Conversion.] AARM NOTE (replacing 13.9.1(13.b)): We permit the use of access-to-variable values that designate constant objects so long as they originate from an aliased variable view of a controlled or immutably limited constant, such as during the initialization of a constant (both via the "current instance" and during a call to Initialize) or during an assignment (during a call to Adjust). [Note: This wording depends on the definition of "immutably limited" from AI05-0052-1.] !discussion It is always possible for an object that is declared constant to internally contain an access-to-variable component that designates a separate heap resident object, meaning that operations on the constant object can still change the state of the "overall" object. If we were to outlaw the idiom of squirreling away an access-to-variable value during the initialization of a constant (the so-called "Rosen" trick), then the programmer would be forced to allocate a separate object in the heap to hold data that needs to be updatable. This would not provide obvious benefit to anyone, since the code would be slower, take more space, and still have exactly the same external interface, namely that an operation can change the state of a "constant." One could argue this would be confusing for non-private types, since potentially visible components of a constant might be updatable, but if the type is non-private, then the semantics of its initialization should certainly be considered open to inspection as well, and the squirreling away of the access-to-variable value should be available for all to see. We use the phrase "*originate* from an attribute_reference applied to a variable view" to allow for possible type conversions after the initial '[Unchecked_]Access that created the access-to-variable value. Hopefully that is clear enough. The key thing is that you have to have a variable view at some point, or else somebody cheated. Moreover, we require that variable view to be aliased and originate from a controlled or immutably limited type, so that uses of 'Address and uses of Address_to_Access_Conversions are excluded from the exception (thus modifying a non-limited untagged type this way is still erroneous). !corrigendum 3.3(13) @drepl An object is either a @i object or a @i object. The value of a constant object cannot be changed between its initialization and its finalization, whereas the value of a variable object can be changed. Similarly, a view of an object is either a @i or a @i. All views of a constant object are constant. A constant view of a variable object cannot be used to modify the value of the variable. The terms constant and variable by themselves refer to constant and variable views of objects. @dby An object is either a @i object or a @i object. Similarly, a view of an object is either a @i or a @i. All views of a constant elementary object are constant. All views of a constant composite object are constant, except for parts that are of controlled or immutably limited types; variable views of those parts and their subcomponents may exist. In this sense, objects of controlled and immutably limited types are @i. A constant view of an object cannot be used to modify its value. The terms constant and variable by themselves refer to constant and variable views of objects. !corrigendum 3.3(25) @dinsa @xindent<@s9 or @b parameter, between its initialization and finalization, if any.>> @dinst @xindent<@s9<6 The value of a constant object cannot be changed after its initialization, except in some cases where the object has a controlled or immutably limited part (see 7.5, 7.6, and 13.9.1).>> !corrigendum 13.9.1(13) @drepl The dereference of an access value is erroneous if it does not designate an object of an appropriate type or a subprogram with an appropriate profile, if it designates a nonexistent object, or if it is an access-to-variable value that designates a constant object. Such an access value can exist, for example, because of Unchecked_Deallocation, Unchecked_Access, or Unchecked_Conversion. @dby The dereference of an access value is erroneous if it does not designate an object of an appropriate type or a subprogram with an appropriate profile, if it designates a nonexistent object, or if it is an access-to-variable value that designates a constant object and it did not originate from an attribute_reference applied to an aliased variable view of a controlled or immutably limited object. An access value whose dereference is erroneous can exist, for example, because of Unchecked_Deallocation, Unchecked_Access, or Unchecked_Conversion. !ACATS test There should be C-Tests that an access-to-variable view of a constant object works properly. !appendix From: Tucker Taft Sent: Wednesday, October 24, 2007 11:26 AM I have attached variant 2 of AI-54 [this is version /01 of the AI - ED.], the AI dealing with the erroneousness of using an access-to-variable value designating a constant object. This variant proposes to eliminate the erroneousness, so long as the value originated from an attribute_reference applied to a variable view of the constant. Such a variable view is provided during initialization for limited and controlled objects. The basic point is that this is an existing idiom, and that disallowing it would just force programmers to allocate a separate heap object to hold data that needs to be updatable, which doesn't do anybody any favors, since you still have the same external interface, namely that an operation can apparently change the "state" of a constant. **************************************************************** From: Randy Brukardt Sent: Wednesday, October 24, 2007 8:26 PM Thanks for doing this. I'm concerned that your AI doesn't cover tasks. Obviously, tasks in constants is a new concept, and it seems that we don't have an allowance for that. task type T is entry Set (A : in Integer); entry Get (A : out Integer); end T; task body T is Val : Integer := 0; begin loop select accept Set (A : in Integer) do Val := A; end Set; or accept Get (A : out Integer) do A := Val; end Get; end select; end loop; end T; type Lim is limited record Tsk : T; end record; procedure Do_It (Obj : in Lim) is begin Obj.Tsk.Set (10); end Do_It; procedure Check_It (Obj : in Lim) is V : Integer; begin Obj.Tsk.Get (V); if V /= 10 then raise Program_Error; end if; end Check_It; declare Obj : constant Lim := (Tsk => <>); begin Do_It (Obj); Check_It (Obj); end; The call to Do_It causes a modification to the local data of the task Obj.Tsk, which is a part of a constant. This surely seems to violate 3.3(13/2) "The value of a constant object cannot be changed." (one presumes that the local values of a task are part of the value of an object containing that task). There doesn't seem to be any penalty for this violation, so maybe it is safe to ignore it, but it seems odd at best. **************************************************************** From: Tucker Taft Sent: Wednesday, October 24, 2007 9:24 PM > I'm concerned that your AI doesn't cover tasks. Obviously, tasks in > constants is a new concept, and it seems that we don't have an allowance for > that. I'm pretty surprised you are worried about this. It has been possible to call entries of task "IN" parameters since Ada 83, so I presumed we never considered the local variables of a task body as part of the "value" of a task object. > ... > > The call to Do_It causes a modification to the local data of the task > Obj.Tsk, which is a part of a constant. This surely seems to violate > 3.3(13/2) "The value of a constant object cannot be changed." Well that isn't quite what 3.3 says, it says you can't change the value between its initialization and its finalization. In any case, I think this is clearly violated by the "Rosen" trick applied to a constant. I think this AI needs to fix this paragraph as well. Perhaps as follows: An object is either a constant object or a variable object. [The value of a constant object cannot be changed between its initialization and its finalization, whereas the value of a variable object can be changed.] Similarly, a view of an object is either a constant or a variable. [All views of a constant object are constant.] {A name that statically denotes a constant object is a constant view. A name that statically denotes a variable object is a variable view.} A constant view of [a variable] {an} object cannot be used to modify [the value of the variable] {its value}. The terms constant and variable by themselves refer to constant and variable views of objects. The net effect is that "constant object" isn't a very interesting term. It basically means that its name denotes a constant view, but that there might be other views that provide a variable view of the same object. > ... (one presumes > that the local values of a task are part of the value of an object > containing that task). I don't make that presumption, and haven't since Ada 83. Perhaps a NOTE to that effect might be friendly. **************************************************************** From: Randy Brukardt Sent: Wednesday, October 24, 2007 9:26 PM > > I'm concerned that your AI doesn't cover tasks. Obviously, tasks in > > constants is a new concept, and it seems that we don't have an allowance for > > that. > > I'm pretty surprised you are worried about this. > It has been possible to call entries of task "IN" > parameters since Ada 83, so I presumed we never > considered the local variables of a task body > as part of the "value" of a task object. An "in" parameter is a constant view of a variable object. I'm thinking about constant objects; recall that 3.3(13/2) says that all views of a constant object are constant. There's no variable view existing anywhere, and that's what's different. Maybe it's hair-splitting. > > ... > > > > The call to Do_It causes a modification to the local data of the task > > Obj.Tsk, which is a part of a constant. This surely seems to violate > > 3.3(13/2) "The value of a constant object cannot be changed." > > Well that isn't quite what 3.3 says, it says you can't > change the value between its initialization and its > finalization. In any case, I think this is clearly > violated by the "Rosen" trick applied to a constant. > I think this AI needs to fix this paragraph as well. > Perhaps as follows: > > An object is either a constant object or a variable > object. [The value of a constant object cannot be changed > between its initialization and its finalization, whereas > the value of a variable object can be changed.] Similarly, > a view of an object is either a constant or a variable. > [All views of a constant object are constant.] {A name > that statically denotes a constant object is a constant > view. A name that statically denotes a variable object > is a variable view.} A constant view of [a variable] > {an} object cannot be used to modify [the value of the > variable] {its value}. The terms constant and variable > by themselves refer to constant and variable views of objects. > > The net effect is that "constant object" isn't a very > interesting term. It basically means that its name > denotes a constant view, but that there might be other > views that provide a variable view of the same object. That would satisfy my concern. > > ... (one presumes > > that the local values of a task are part of the value of an object > > containing that task). > > I don't make that presumption, and haven't since Ada 83. > Perhaps a NOTE to that effect might be friendly. Well, they certainly aren't part of the enclosing object in our implementation. But I can't find anything in the language semantics that says that. Just because we all think something is true does not make it true! But I do prefer your rewording of 3.3(13/2), because it will help avoid misconceptions. **************************************************************** From: Tucker Taft Sent: Wednesday, October 24, 2007 11:08 PM >> I'm pretty surprised you are worried about this. >> It has been possible to call entries of task "IN" >> parameters since Ada 83, so I presumed we never >> considered the local variables of a task body >> as part of the "value" of a task object. > > An "in" parameter is a constant view of a variable object. I'm thinking > about constant objects; recall that 3.3(13/2) says that all views of a > constant object are constant. There's no variable view existing anywhere, > and that's what's different. Maybe it's hair-splitting. But the more fundamental part of 3.3 is the part that says you can't use a constant view to modify an object. But you *can* call an entry on a constant view of a task object (even in Ada 83), so clearly calling an entry is *not* considered "modifying" a task object. Hence the local variables of a task body must clearly not be considered part of the "value" of a task object. I don't think we need any hand waving to prove that, based on semantics that have existed since Ada 83. >>> ... >>> >>> The call to Do_It causes a modification to the local data of the task >>> Obj.Tsk, which is a part of a constant. This surely seems to violate >>> 3.3(13/2) "The value of a constant object cannot be changed." >> Well that isn't quite what 3.3 says, it says you can't >> change the value between its initialization and its >> finalization. In any case, I think this is clearly >> violated by the "Rosen" trick applied to a constant. >> I think this AI needs to fix this paragraph as well. >> Perhaps as follows: >> >> An object is either a constant object or a variable >> object. [The value of a constant object cannot be changed >> between its initialization and its finalization, whereas >> the value of a variable object can be changed.] Similarly, >> a view of an object is either a constant or a variable. >> [All views of a constant object are constant.] {A name >> that statically denotes a constant object is a constant >> view. A name that statically denotes a variable object >> is a variable view.} A constant view of [a variable] >> {an} object cannot be used to modify [the value of the >> variable] {its value}. The terms constant and variable >> by themselves refer to constant and variable views of objects. >> >> The net effect is that "constant object" isn't a very >> interesting term. It basically means that its name >> denotes a constant view, but that there might be other >> views that provide a variable view of the same object. > > That would satisfy my concern. Glad to hear it. > >>> ... (one presumes >>> that the local values of a task are part of the value of an object >>> containing that task). >> I don't make that presumption, and haven't since Ada 83. >> Perhaps a NOTE to that effect might be friendly. > > Well, they certainly aren't part of the enclosing object in our > implementation. But I can't find anything in the language semantics that > says that. Just because we all think something is true does not make it > true! No, but see my rationale given above. **************************************************************** From: Jean-Pierre Rosen Sent: Thursday, October 25, 2007 7:27 AM > The call to Do_It causes a modification to the local data of the task > Obj.Tsk, which is a part of a constant. This surely seems to violate > 3.3(13/2) "The value of a constant object cannot be changed." (one presumes > that the local values of a task are part of the value of an object > containing that task). > > There doesn't seem to be any penalty for this violation, so maybe it is safe > to ignore it, but it seems odd at best. Well, a constant can include a pointer to a variable, whose value can also be changed. Not exactly the same thing, but I think close enough that we don't have to care about it. **************************************************************** From: Erhard Ploedereder Sent: Sunday, February 3, 2008 6:29 PM My original intent to constrain legitimizing assignments to constants via the Rosen trick to constants of private type fell prey to generic contract issues, as the minutes already opined. (Or else I would have to impose so much load on implementing the variable-views-to-constants that the proposal would be flat on its face anyhow). Instead of revising AI-54 in ways that have not been discussed at a meeting, I am submitting a comment on the AI, since I have several issues with this AI-54/02..... The modification of 3.3(14) with its new notion of "updating a variable" may be technically opportune (and work), but it violates the "least surprise" principle of language design. "If the value of a variable changes by any operation, the variable has been updated" is something that 100% of computer scientists will sign as a true statement. It would no longer hold in Ada. Instead, the notion that "updating a variable" by assignment is dependent on the view of the type of a component whose value is changed is completely counter to the common interpretation of what updating a variable means. (Besides, I still am uncertain about all the ramification that are implied by "... the variable if its type is not a descendent of a partial view of a full or actual type"; I claim that max. 50% of the ARG and 0,x% of the Ada users understand the implications.) ---- Side remarks: 1. assuredly, this needs to say "object" instead of "variable" (or else the intended effect for the modification of constants is not achieved); 2. "its type" binds ambiguously both to "part" and "variable". ----- I fell into this trap, too, since for the longest time, I then assumed that now assigning to said excepted parts would be allowed. But no, it isn't: Assigment semantics also requires a variable view. Consider procedure Foo(X: in T) is begin null; X.M.Gen.Z := 8; -- Rosen trick; X.M.Gen is an acc-to-var view of X X.Z := 8; -- still illegal, since X is not a variable view end Foo; and assume that Obj.M.Gen is always initialized such that it self-references Obj for any Obj. Assume further that all object-declarations in the universe carry a "constant" keyword. I had a large example prepared to show how the latter assignment causes problems, but then I found out that it was illegal by this other rule in 5.2. So, let's get this straight as per the revised 3.3(14): "The first assigment does not update X, although it changes the value of the Z component of X, but it updates Z. The second assignment would change the value of the Z component of X and thus update Z and X, but it is illegal to do so." Technically correct, but changing the same component by two different syntaxes has two different semantics? That is very interesting. How does one teach that to anyone? Did you believe the first quoted statement? It actually is wrong and again, I fell into that trap. If X happens to be passed by value, then X.M.Gen.Z still refers to the Z component of the actual, while X.Z refers to the Z component of the formal copied from the actual. Only if X is passed by reference are the Zs the same (which is the case for the Rosen trick with limited or tagged types, but not necessarily any more if merely an access-to-variable of a constant has been obtained and squirreled away elsewhere, i.e., not in an access discriminant. Note that the permission to use access-to-variable views on constant objects (as per the modified 13.9.1(13)) is not restricted to limited or by-reference types. Built-in-place extended results as initializations of constants are a case in point. They need not be limited, yet any access value obtained in the process would be legitimate. On a secondary note, this AI, if approved, would make code optimization or any program analyses incorrect that assume equal values of a constant entity at all places after its declaration. This is completely counter-intuitive and may require major compiler, analyzer and backend reworks, let alone rewiring of people's brains. For every one of these three reasons, a different way to legitimize the Rosen trick needs to be found if legitimizing is really desirable. (Personally, I find the conceptual and real costs of legitimizing the idiom much too high in anything that I have seen so far.) note bene: I am not opposed to making the constant/variable view notion more explicit, as it is done with the 3.3(13) rewrite and with the add-on after 3.3(14). I am merely opposed to have constant views that somehow can be perverted into lasting variable views (and dead set against perverting notions like "updating a variable" to special needs). --- Another side remark --- If the add-on after 3.3(14) is kept in the final AI, it also needs to mention Pragma Volatile on constants as an exception. --------------------------- Solutions ?: One possibility is to "upgrade" the access-to-variable modification of a constant to be a bounded error rather than erroneous: For subsequent uses of the constant either the assigned value or the old value is used. While not a deterministic semantics, it gets rid of the erroneousness. It also reflects the "actual events" of today, i.e., the cacheing of constant values at compile- or runtime. **************************************************************** From: Randy Brukardt Sent: Monday, February 4, 2008 11:06 PM ... Instead of revising AI-54 in ways that > have not been discussed at a meeting, I am submitting a comment on the AI, > since I have several issues with this AI-54/02..... > > The modification of 3.3(14) with its new notion of "updating a variable" may > be technically opportune (and work), but it violates the "least surprise" > principle of language design. Huh? There is no modification to 3.3(14) in any latest version of AI05-0054-1 or AI05-0054-2. And I don't see any way that the proposed change to 3.3(13) would have any effect. Ada has always had constant views of variable objects, so nothing new there. >"If the value of a variable changes by any operation, the variable has been > updated" is something that 100% of computer scientists will sign as a true > statement. It would no longer hold in Ada. Instead, the notion that > "updating a variable" by assignment is dependent on the view of the type > of a component whose value is changed is completely counter to the > common interpretation of what updating a variable > means. (Besides, I still am uncertain about all the ramification that are > implied by "... the variable if its type is not a descendent of a partial > view of a full or actual type"; I claim that max. 50% of the ARG and 0,x% of > the Ada users understand the implications.) This makes no sense. There is no wording like "... the variable if its type is not a descendent of a partial view of a full or actual type" in any of the AIs. What are you reading??? ... > note bene: I am not opposed to making the constant/variable view notion > more explicit, as it is done with the 3.3(13) rewrite and with the add-on > after 3.3(14). I am merely opposed to have constant views that somehow can > be perverted into lasting variable views (and dead set against perverting > notions like "updating a variable" to special needs). There is no "add-on after 3.3(14)" in any version of the AI that I've seen. Perhaps that is adding confusion where none is necessary... **************************************************************** From: Tucker Taft Sent: Monday, February 4, 2008 11:40 PM I'll admit I didn't follow all of what Erhard wrote, but I agree that we should change the second sentence of 3.3(14) as follows: ... The value of [a variable] {an object} is updated when an assignment is performed to any part of the [variable] {object}, or when an assignment is performed to an enclosing object. **************************************************************** From: Erhard Ploedereder Sent: Wednesday, February 6, 2008 6:01 AM > Huh? There is no modification to 3.3(14) in any latest version of > AI05-0054-1 or AI05-0054-2. And I don't see any way that the proposed > change I was working off a paper copy that had these mods to 3.3(14). Tracking this back to its origins, it turns out to be a copy that I modified at the DC meeting with results of the discussion at the time. So I was biting my own tail. Sorry to have caused confusion. I will rewrite the comment to refer to the most recent recorded version of the AI. **************************************************************** From: Erhard Ploedereder Sent: Friday, February 8, 2008 5:32 AM I would like to ask for withdrawal of AI-54 from the Tampa agenda, since I would like to participate in its further discussion. The arguments in the AI so far are misleading. For example, they imply that somehow the Rosen trick is deemed erroneous by current semantics. That's false. It is perfectly legitimate today for most of its uses (namely, getting around the fact that an IN parameter in some abstraction needs to have modified internal state), as long as the actual parameter is not declared to be a constant. Note that 13.9.1(13) does not apply in this case. It only applies if the original object behind the views has been declared constant. (Aside: I had not realized this before, but the Rosen trick, if it is applied to non-limited or non-tagged types, is even resilient to the actual parameter passing mechanism, since the acc-to-var will always point to the original object, both in by-ref and by-val.) Furthermore the proposed change trades the safety principle that I can trust specifications, in this case the "constant" specification on an object declaration, against avoiding erroneousness in a quite dubious and rare sitation, namely the modification of said constant by the Rosen trick. This is a REALLY BAD tradeoff, when a specification can be violated in order to legitimate an obscure and, for constant, very dubious idiom. It has been argued that "constant" for an object declaration does not mean constant object, but only constant view of the object. Today is does not, however, and I will counterargue that this would be a HUGE upward incompatibility plus a real design mistake. Even if the change is not incompatible in terms of canonical execution semantics, I can come up with examples of incompatibilities thanks to legitimate optimizations like copy propagation, which clearly are allowed to rely on the constantness of constants so far, but would no longer. But it is certainly an incompatibility for any analysis or verification tool in existence. I do not know of any such tool that would not immediately take advantage of asserted constantness. (Tuck, how about your tools?) The design mistake, of course, is that the language would either loose constants alltogether (probably true, thanks to extended return statements and the fact that the proposed revision of 13.9.1(13) legitimizes even Unchecked_* accesses in the case in question), or create two classes of constant objects: the class of the real constants and the class of the variable constants. What a great idea. In short, I will argue for "no action" on this AI. (And again, if someone really wants to get rid of erroneousness, make it a bounded error. But an error it is, discovered or not.) **************************************************************** From: Edmond Schonberg Sent: Friday, February 8, 2008 6:45 AM > I would like to ask for withdrawal of AI-54 from the Tampa agenda, > since I would like to participate in its further discussion. > > The arguments in the AI so far are misleading. Understood. We'll postpone discussion of this one until the Venice meeting. (an refrain from using the Rosen trick except in dire emergencies). **************************************************************** From: Robert A. Duff Sent: Monday, February 11, 2008 3:20 PM > (Aside: I had not realized this before, but the Rosen trick, if it is > applied to non-limited ... The Rosen trick works only for limited types. That is, the following: type T is record Self : access T := T'Unchecked_Access; -- Illegal! end record; is illegal, because the current instance of a nonlimited type is not aliased. >...or non-tagged types, is even resilient to the > actual parameter passing mechanism, since the acc-to-var will always > point to the original object, both in by-ref and by-val.) **************************************************************** From: Erhard Ploedereder Sent: Saturday, February 16, 2008 8:02 AM > The Rosen trick works only for limited types. True in its original form of using an access discriminant as part of default initializion. False, if "Rosen Trick" is equated with "gain an access-to-variable to a constant during initialisation by whatever tricky means." AI-54 is about the latter, not the former. Why false? Consider the following example, where nothing in sight is limited and yet I have variable access to a constant object. I give you the full compilable and runnable example... with Ada.Finalization; use Ada.Finalization; package ConstantsC is type T is private; private type FatArray is array (1 .. 10000) of Integer; type T is new Controlled with record Z : Integer := 5; Comp : access T; Weight : FatArray := (others => 1); end record; procedure Foo (X : in T); procedure Adjust (X : in out T); end ConstantsC; with Text_IO; package body ConstantsC is procedure Foo (X : in T) is begin X.Comp.Z := 8; end Foo; function Inplace return T is begin return X: aliased T do X.Comp := X'Unchecked_Access; end return; end; procedure Adjust (X : in out T) is begin X.Comp := X'Unchecked_Access; end Adjust; X : constant T := Inplace; begin Foo(X); Text_IO.Put_Line(Integer'Image(X.Z)); end ConstantsC; with ConstantsC; procedure TestconstantsC is begin null; end TestconstantsC; The FatArray in the example is a relict of my (failed) attempts to "convince" GNAT to initialize in place for a non-limited type. If that were to happen, and I don't think that the language prohibits this optimization, the "trick" would work even for types that are not by-reference, i.e. pretty much any type. In that case, there really are no more constants. **************************************************************** From: Edmond Schonberg Sent: Saturday, February 16, 2008 8:38 AM > Why false? Consider the following example, where nothing in sight is > limited and yet I have variable access to a constant object. > I give you the full compilable and runnable example... Very cute! However, AI-53 removes the "aliased" from extended return statements, so the example is in fact illegal. True constants are safe .. for another few seconds at least. From a more pragmatic point of view, this means that types with self- references cannot be treated as constants by a static analyzer, because in a sense they have some state. Is this such a huge loss? **************************************************************** From: Erhard Ploedereder Sent: Saturday, February 16, 2008 9:04 AM Several problems: "types with self-references": that is an undecidable property; The "access T" component may point to some other T object and that would be perfectly fine in a true constant. Moreover, it needn't be self-reference, it can be an arbitrary cycle. I can squirrel away the access-to-variable value in some other object (I think), maybe even one that I create during initialization for that very purpose and that I point to. I.e., my type has an access T2 component; T2 has an access T component. And finally, cries of breach of privacy. Although I can live with that one. **************************************************************** From: Tucker Taft Sent: Saturday, February 16, 2008 9:39 AM I believe the only types to which this issue arises are "immutably" limited types and controlled types, since during their initialization, limited types are writable and aliased from "inside" the type declaration, while controlled types are writable and aliased from "inside" the initial call on "Initialize" or "Adjust." Fundamentally it seems *much* safer to require that implementations recognize that constants of such types are potentially updated through "general" access values, than to somehow declare that the preservation of such access values past an object's initialization phase is erroneous and "programmer beware." Given that the Rosen trick as applied to limited types has been a well-established technique during Ada 95, it seems hopeless to outlaw it now. Furthermore, given "privacy" concerns, an Ada 95 programmer might have taken advantage of it, and then an Ada 2005 programmer might have taken advantage of declaring a constant of such a limited type without studying the private part of every package and generic package that might be relevant to know that the Rosen trick is in play. As Ed points out, the more general loophole associated with extended return has been closed, and I agree that if it had not been closed, we would have a much bigger problem on our hands. But if you limit this to immutably limited types and controlled types, the Rosen trick seems like a reasonable engineering approach to avoid having to introduce a separate writable component in the heap to implement a particular abstraction that has a well-defined notion of what it means to be "partly constant." For example, you can "Read" and "Write" using a constant of a "File_Type" in Direct_IO, but you can't "Open" or "Close" it. Be that as it may, we know that Read and Write advance the Index associated with the File_Type, even though the File_Type is an "in" parameter. Clearly the Index must be maintained in a part of a "constant" File_Type that is updatable. Must this necessarily be in the heap? **************************************************************** From: Erhard Ploedereder Sent: Saturday, February 16, 2008 9:46 AM Ed, you are making me jump through hoops here. ... :-) The following, without aliased in the extended return, and still compliant with the revised 13.9.1(13) of AI-54/2, and still an access-to-variable to a constant and still not a limited type. More or less for the archive of AI-54-2/3 to deal with this case as well in any final wording. Is this an issue for AI-53 as well? with Ada.Finalization; use Ada.Finalization; package ConstantsC2 is type T is private; private type Selfptr is access all T; type Handle_Type (Gen : Selfptr) is limited null record; type SidePtr is access all Handle_Type; type T is new Controlled with record Z : Integer := 5; Comp : sidePtr; end record; procedure Foo (X : in T); procedure Adjust (X : in out T); end ConstantsC2; with Text_IO; with System.Address_to_Access_Conversions; package body ConstantsC2 is package P is new System.Address_To_Access_Conversions(T); procedure Foo (X : in T) is begin X.Comp.Gen.Z := 8; end Foo; function Inplace return T is begin return X: T do X.Comp := new Handle_Type(selfPtr(P.To_Pointer((X'Address)))); end return; end; procedure Adjust (X : in out T) is begin X.Comp := new Handle_Type(selfPtr(P.To_Pointer((X'Address)))); end Adjust; X : constant T := Inplace; begin Foo(X); Text_IO.Put_Line(Integer'Image(X.Z)); end ConstantsC2; with ConstantsC2; procedure TestconstantsC2 is begin null; end TestconstantsC2; **************************************************************** From: Robert Dewar Sent: Saturday, February 16, 2008 10:16 AM > As Ed points out, the more general loophole associated > with extended return has been closed, and I agree that > if it had not been closed, we would have a much bigger > problem on our hands. But if you limit this to > immutably limited types and controlled types, the Rosen > trick seems like a reasonable engineering approach to > avoid having to introduce a separate writable component > in the heap to implement a particular abstraction that > has a well-defined notion of what it means to be > "partly constant." Sounds reasonable to me, I assume you are referring to the "Rosen technique" in the above? :-) **************************************************************** From: Erhard Ploedereder Sent: Saturday, February 16, 2008 11:18 AM Let me summarize the points that I am trying to make with the examples... Point 1: Clearly, it is not just limited types but also non-limited controlled types that are subject to the problem that access-to-variables can be used to modify a constant. This is independent of AI-53. Point 2: AI-54-2/3 in its re-formulation of 13.9.1(13) is very general in nature. So much so that already today I can squeeze through unintended holes. First I squeezed past limitedness. After all, "Rosen works only for limited types". Wrong! (Ed, sorry for confusing the issue by also involving in-place initialization in the example. I did not need the "aliased" for the access-to-variable generation by Adjust.) Then I squeezed past the missing AI-53 "aliased" for the hypothetical in-place initialization case for non-limited constants, which, however it is achieved, will give me variable access to all constants such initialized. And nobody has told me that such in-place initialization is forbidden. It is a very fragile net that is woven by AI-54 to allow access-to-variable on constants in some but not all cases. --------------------------------- So, to legitimize Ada95 Rosen for constants, at least try to not generalize it beyond limited types. I could live with a notion that limited constants are an obsolete feature of the language, with the possibility that such constants aren't constants, while the feature is still supported. But, if I have to give up on controlled constants and who-knows-whatelse constants because the above creates generic contract problems, then I'd rather see the Rosen Trick for constant limited objects to stay erroneous (or become a bounded error as I proposed) than to see declared constants of various kinds become variable. The Rosen trick for constant objects has been erroneous for 12 years now or maybe even longer. Why should we be willing to damage the concepts of constants to change it into legitimate behavior? **************************************************************** From: Tucker Taft Sent: Saturday, February 16, 2008 11:27 AM Could you answer the general question of whether you think it is a legitimate engineering solution to use the Rosen "technique" to avoid having to use the heap. Being an implementor of a highly optimizing compiler, I certainly understand your concern. On the other hand declaring something erroneous is the ultimate "penny in the fusebox" solution to a situation like this. **************************************************************** From: Erhard Ploedereder Sent: Saturday, February 16, 2008 11:50 AM > ... the Rosen trick seems like a reasonable engineering approach to > avoid having to introduce a separate writable component > in the heap to implement a particular abstraction that > has a well-defined notion of what it means to be > "partly constant." But see, that is not how the Rosen trick is applied in almost all cases. It is applied to get around the constant view that an IN parameter imposes, not around the constantness of the object itself. From the client side a constant object of a limited type is a bit of a tautology, isn't it? You cannot assign to a limited object or assign to one of its components. So why would you declare an object of this type constant? So that you cannot assign to it or one of its components, because you don't trust limited to do the job? If you do consider limited to be an abstraction that says: "client shall not change this object; changes are reserved to the server." then a "constant" declaration by the client is in some sense a violation of the contract, since the client interferes with the ability of the server to do as needed, and on the client side it is a useless restriction, since the "constant view for the client" is already enforced by "limited". Which is why I would rather see "constant" on limited objects be the punished culprit than the perfectly legitimate and useful "constant" on controlled objects or any other that we might still discover to be subject to access-to-variable semantics with what is currently proposed. And, yes, "there are billions and billions constant limited objects out there". I believe it when I see it. Just as there were billions and billions of delta constraints, who still live happily ever after in Annex J land. A lesser punishment would indeed be to send only those constant limited objects that have a Rosen flavor into the neighboring country of Bounded-Error land. **************************************************************** From: Robert Dewar Sent: Saturday, February 16, 2008 12:38 PM >> ... the Rosen trick seems like a reasonable engineering approach to >> avoid having to introduce a separate writable component >> in the heap to implement a particular abstraction that >> has a well-defined notion of what it means to be >> "partly constant." > > But see, that is not how the Rosen trick is applied in > almost all cases. It is applied to get around the constant > view that an IN parameter imposes, not around the constantness > of the object itself. Well anything that helps us get past this extremely annoying restriction in Ada is worthwhile (we have quite a lot of uses of Unrestricted_Access to deal with this case :-)) **************************************************************** From: Dan Eilers Sent: Saturday, February 16, 2008 12:55 PM > Could you answer the general question of whether > you think it is a legitimate engineering solution > to use the Rosen "technique" to avoid having to > use the heap. I don't consider the Rosen "technique" to be "legitimate engineering". It is at best a kludge, involving unnecessary space and time overhead, and reducing code readabilty. It seems that Ada could avoid the need for such kludges by providing syntax to indicate that a field of an otherwise constant record is mutable. This would elegantly solve the problem for File_Type, and and also the similar problem for mutable generator state in the random number packages. **************************************************************** From: Erhard Ploedereder Sent: Saturday, February 16, 2008 1:27 PM > Could you answer the general question of whether > you think it is a legitimate engineering solution > to use the Rosen "technique" to avoid having to > use the heap. Sure I can answer this. It is an o.k. engineering solution to get around the limitation imposed by (limited) IN parameters, since indeed I may have modifiable views on the variable actual parameter. Do I want it for that to avoid using the heap? Given such IN parameters, sure, I want it. But then, I already have it. Well defined, legal and all, ever since the Rosen Trick was invented. I keep saying that this covers >90%, if not 99,99% of the territory. (Don't worry, I'll come back to this.) It is not an o.k. engineering solution to get around the limitation imposed by constantness of an object. And I do not need that engineering solution, since I do not have the problem in the first place, because I do not make limited objects constant. What for would I do this? Ok, ok, others might. Engineering solution or not for a problem, a sanctioned solution shall never violate a specification. The latter is the very definition of an error, diagnosed or not. Where we differ is in how far we are willing to change the specification (of constants) to allow for the Rosen trick to be applied to objects whose constantness has been specified at the point of creation. If one argues that the user cannot/should not know whether portions of a limited object can be changed, he ought not be allowed to call it a constant. Everything else would then already be in place for the engineering solution to avoid the heap. The constant view is already enforced for his side of the fence by the limitedness. The only thing that stands in the way is the upward-compatibility issue. Therefore my view of going the Annex J or the Bounded-Error route, but not mess with the notion of constant objects. Your view seems to be that the Rosen Trick cannot be applied unless the language guarantees that it also works for constant objects, since there could be some future user who would be surprised if he could not make his limited object constant. I have no sympathy for this future user. He should not even consider this possibility. Yes, I realize that there is a fine point: constants cannot be IN OUT parameters. But to say that the user ought be able to prevent IN OUT changes of objects, while being forced to accept hidden changes to IN parameters, is a bit hair-splitting to me. As we are discussing this, you see my position shifting gradually to also consider prohibiting constant limited objects altogether, probably along the Annex J route. It would avoid a tie-in to whether or not Rosen was applied on the other side of the fence. But then, we are considering AI-53 with all those billions and billions of "aliased" extended returns already in place; maybe we can be more rigorous about it and just plain forbid these constants :-) **************************************************************** From: Edmond Schonberg Sent: Saturday, February 16, 2008 4:35 PM > Ed, you are making me jump through hoops here. ... :-) I can only admire your agility, but if it takes two levels of indirection, an unchecked conversion, and an access discriminant on a non-limited type to make use of this language hole, it cannot be a very large one, and the solution should be equally modest. Declaring such access a bounded error seems appropriate. > The following, without aliased in the extended return, and still > compliant with the revised 13.9.1(13) of AI-54/2, and still an > access-to-variable to a constant and still not a limited type. Yes, it's instructive that neither self-reference nor limitedness are needed. With a bit of effort you might be able to do without controlled types as well :-)! **************************************************************** From: Erhard Ploedereder Sent: Saturday, February 16, 2008 6:48 PM > With a bit of effort you might be able to do without controlled types > as well :-)! That is exactly what I am afraid of, i.e., interactions that aren't necessarily there today, but might be tomorrow. That is why I am saying that "access-to-variable is o.k. for constants, as long as they were obtained from a variable-view of the constant" is such an indirect way of trying to capture an intent to allow them for limited constants, that dragons live there. Some of them are Controlled, as we now know. **************************************************************** From: Tucker Taft Sent: Saturday, February 16, 2008 9:43 PM I'll admit to have pretty much lost the thread here. If we look at the particular case of Direct_IO.File_Type, someone made the decision that they wanted to have the IN vs IN-OUT distinction for these types correspond to allowing read/write vs. allowing open/close. We all seem to have gotten used to that, and it seems vaguely reasonable. Of course if Ada had a whole "capability-based" system, we could split hairs much more finely. But all we have is IN vs. IN-OUT, and we allow the implementor of an abstraction to require variables for some operations and allow constants for others. Once you accept that it is up to the abstraction designer to make this distinction between operations that have IN parameters and those that have IN-OUT parameters, it seems that the notion of "constant" has to be interpreted in that context as well. That is, "constant" simply means that you can only pass the object to operations that take IN parameters. It really doesn't guarantee anything about whether all parts of the state are immutable. Hence, my conclusion is that "constant" should mean nothing more than that which the language enforces, namely that you can't use a name that statically denotes a constant declaration to pass (explicitly) an object to an operation that uses "IN OUT" mode. Period. End of story. Interpreting "constant" to mean anything more than that seems like a recipe for the programmer and the compiler to have divergent views of reality, which is never a good thing. If a programmer only passes an object to operations that take IN parameters, then several compilers encourage the programmer to declare such an object a constant. It would be weird if having made an effort to make limited and nonlimited work more alike in Ada 2005, we eliminate the ability to declare limited objects constant when they are initialized upon declaration, and thereafter only passed to operations that have IN mode. Declaring something erroneous only makes sense in a language like Ada if we think the programmer has to stand on their head and go out of their way to do something, and trying to make the compiler survive all such craziness is too difficult, and we can't figure out any way to make what such a programmer is doing illegal. Using the access discriminant of a component to point at the enclosing object is documented as a reasonable thing to do, and described in the Ada 95 rationale. Similarly initializing an access component of a controlled type to point to the object itself during the Initialize routine seems quite reasonable as well, for example to create the head of a circularly linked list. If I then declare a constant of a type Circularly_Linked_List, initialized by calling a function that returns a pre-built list, I might choose to have some operations that logically preserve the contents of the list that use IN mode, and some that add or remove elements that use IN-OUT mode, but it might not be unreasonable for the IN mode operations to do some amount of modification to implement some kind of move-to-front optimization to speed access. Who knows? If things can be done easily and naturally in the language, it seems very unsafe to declare them as erroneous. I am happy to limit this explicitly to controlled and immutably limited types, as those are the two that provide variable, aliased views naturally. Would that satisfy your concerns? I don't see any generic contract issue here, since the aliased variable view is only provided in cases where you have a full view on the type. All of the other examples almost certainly involve standing on one's head, and/or using Unchecked_Conversion or Address_To_Access_Conversion. FWIW I consider Address_To_Access_Conversion to be Unchecked_Conversion on steroids -- I have forgotten why we didn't call it "Unchecked_Address_To_Access_ Conversion" ... seems like a big mistake at the moment. I am happy to load up the description of this generic with all kinds of erroneousness caveats, and to encourage all style-checking tools to treat it as at least as bad as Unchecked_Conversion. **************************************************************** From: Randy Brukardt Sent: Saturday, February 16, 2008 11:03 PM Erhard is seriously confused and writes: > Let me summarize the points that I am trying to make with the examples... > > Point 1: > Clearly, it is not just limited types but also non-limited controlled types > that are subject to the problem that access-to-variables can be used to > modify a constant. This is independent of AI-53. AI-53 doesn't have anything to do with this at all - please don't muddy the waters. Moreover, this is a capability available in Ada 95, and that has been used there. In particular, Claw uses Initialize to hang newly created objects on various lists. Those lists all use access-to-variable types. Surely someone could declare a Claw object as a constant! How could Claw prevent or detect it? Your position is that such a program is erroneous, because it doesn't make sense for someone to do that (declare an object a constant). That may be true, but our philosophy with Claw was to make the interface as idiot-proof as possible (that is, we do everything possible to avoid having the program die without reason). Ada doesn't provide us any way to detect that a constant was created, and allowing such things to be erroneous simply allows a compiler to generate trash if the user does this. I know darn well where that bug report is going to end up, and it isn't going to be the user nor the compiler writer that has this pain. > Point 2: > AI-54-2/3 in its re-formulation of 13.9.1(13) is very general in nature. > So much so that already today I can squeeze through unintended holes. > > First I squeezed past limitedness. After all, "Rosen works only for > limited types". This has nothing to do with limitedness, nor really with the Rosen trick. The Rosen trick is just one way it shows up. Indeed, this also appears whenever a type has an active component (like a task) or potentially whenever a composite type has a component of an access-to-variable type. This later case has existed in programming languages since the pointer was invented. I'm not sure why you think that something new is going on! If an composite type has an access-to-variable component, it might very well consider what is at the end of that pointer logically part of the object. And in that case, any operation on the type can change the values in the portion of the object, even if it is IN parameter, and even if the object is declared constant. (Claw does this, too, BTW, in order to share data between objects that represent the same window). There is nothing different about this case and the one in question: part or all of the (logical) object can be modified even if it is declared constant. So why is that so bad? ... > Then I squeezed past the missing AI-53 "aliased" for the hypothetical > in-place initialization case for non-limited constants, which, however it is > achieved, will give me variable access to all constants such initialized. > And nobody has told me that such in-place initialization is forbidden. Of course it is all composite objects that have an access component that can have this happen. There is nothing new about that (see above). No "squeezing" needed!! What I don't understand is what you are trying to "save" here. You've talked about optimizations and analysis tools, but your examples aren't convincing simply because it has been trivial since the introduction of the pointer for their to be constant composite objects that have mutable parts. Any tool or optimizer that doesn't take that fact into account is seriously broken. The only way to make such assumptions is to look at the details of the type (that is, to break privacy), and even then, the presence of any access component eliminates the possibility of the object from being "constant" in the sense that a tool would want to use. ... > --------------------------------- > > So, to legitimize Ada95 Rosen for constants, at least try to not > generalize it beyond limited types. That doesn't work, because of controlled initialization, which is an obvious way to construct resilient abstractions. If you're saying that a system like Claw is poorly engineered because a user mistake [declaring an object constant] allows a compiler to generate utter garbage and destroy the invariants of the interface (and by corollary that I'm a poor engineer for choosing that design), I have a response for you, but it is not fit for public consumption. > I could live with a notion that limited constants are an obsolete feature of > the language, with the possibility that such constants aren't constants, > while the feature is still supported. They can hardly be obsolete: we just added them in Ada 2005! We did that because people wanted them. But I don't think that they cared particularly if they meet some bizarre notion of composite constantness... > But, if I have to give up on controlled constants and who-knows-whatelse > constants because the above creates generic contract problems, then I'd > rather see the Rosen Trick for constant limited objects to stay erroneous > (or become a bounded error as I proposed) than to see declared constants of > various kinds become variable. There never were controlled constants in Ada; if you think otherwise you were sadly misled. (Of course, a particular type can be truly constant if there is no aliasing and no access types around, but not in general.) > The Rosen trick for constant objects has been erroneous for 12 years now or > maybe even longer. Why should we be willing to damage the concepts of > constants to change it into legitimate behavior? There are no such "concepts" for composite objects in Ada or any other programming language that has pointers. Only scalar objects can be truly constant. The erroneousness in this case was always a bug in the language. If the language was not going to prevent (by raising an exception, since it can't be statically checked) the creation of access-to-variable views, then it has to allow them to work. Erroneousness is always bad! It allows absolutely anything to happen. And if we know how something should work, and there is no implementation problem with having it work (and that is the case here), then why should we allow anything to happen? As Robert previously noted, there are two kinds of erroneousness: cases that language-lawyers think are erroneous, but are used and generally will work; and cases where there is a genuine problem. This is surely one of the former (rather than latter) cases. I could live with a bounded error (from the perspective of Claw) IF such an error allowed either the raising of Program_Error or working as written (that is, as an access-to-variable). Allowing the wrong value to be accessed is as bad as erroneousness in this case. But I suspect that that solution would be considered wildly incompatible, as it would make almost all programs containing limited and controlled types subject to bizarre (and non-portable) exceptions being raised for no obvious reason. And it is a privacy breakage and maintenance hazard: whether or not a user can declare a constant of some type depends on the details of the type. And implementing the actual check would have a distributed overhead (you'd need a constant bit with either objects or parameters of controlled types so that the check could be made in user-defined Initialize/Adjust/Finalize.) So I think in fact I cannot live with a bounded error. The only solution that I see would be to somehow include this in the contract (that is, in the partial view). But that seems a very heavy-weight solution to a light-weight problem -- and, again, it is incompatible with lots of existing code. **************************************************************** From: Erhard Ploedereder Sent: Sunday, February 17, 2008 8:25 AM Let's agree that we disagree. But for the sake of others, let's describe our positions in simple ways. Here is my version. I am trying to save the notion that objects declared to be constants are indeed constants and that this asserted invariance really holds. It can be broken only by clearly identified Unchecked programming practices, which then are deemed erroneous. This is the way it has been since 1980 in this language. To appease Randy and his fear that was has worked in practice for 12+ years might suddenly stop working, I tried the Bounded Error route in lieu of erroneousness, but I refuse to change the existing notion of a constant object as a truly invariant object. Tuck and Randy want to legitimizing changes to constant objects in situations where the Rosen Trick or its variants was needed (mostly on IN parameters). The Rosen Trick has been legal only for variable actuals of these IN parameters, and erroneous for constant actuals. It is a useful idiom to avoid heap usage for hidden modifiable state, where stack usage suffices just as well. To legitimize changes to constant objects, the notion of asserted invariance has to disappear from the language. Tuck's model changes the semantics of a constant object declaration to only introduce a constant view of a variable object. There are no more constants (in the usual sense of the word) in Ada. In many cases presently, but possibly changing over time as the language evolves, such a constant view may still guarantee invariance of the underlying object. The predicate for invariance becomes "the object declaration has the constant keyword and nowhere has the user succeeded to "legitimately" gain an access-to-variable view on the object or has not used such a view later to change the object". The "legitimately" part is presently worded in AI-54 as "anything goes via legal attribute_references of any kind as long as it is during initialization or finalization", but we might still narrow this some. Did I summarize correctly? **************************************************************** From: Erhard Ploedereder Sent: Sunday, February 17, 2008 8:42 AM Ooops, I misrepresented: > object". The "legitimately" part is presently worded in AI-54 as "anything > goes via legal attribute_references of any kind as long as it is during > initialization or finalization", but we might still narrow this some. should read: object". The "legitimately" part is presently worded in AI-54 as "anything goes via legal attribute_references applied to a variable view of the object (e.g., during initialization, finalization or access to the object via an access-to-variable view)", but we might still narrow this some. (I just realized that of course you can get additional access-to-variable views in the midst of the object's lifetime, as soon as you have one of them.) **************************************************************** From: Tucker Taft Sent: Sunday, February 17, 2008 9:35 AM I would suggest altering the new phrase proposed in AI-54 for 13.9.1(13) as follows: ... or if it is an access-to-variable value that designates a constant object and it did not originate from an attribute_ reference applied to a{n aliased} variable view of the object This makes it clear that we can't use 'Address to bypass the requirement for being aliased. An implementation that doesn't want to do further analysis therefore has to presume that all immutably limited and controlled objects are modifiable, as these are the types for which an aliased variable view can exist even if the object is declared "constant." Of course you already have to be careful about controlled objects in Ada 95 since you need to recognize that Initialize, Adjust, and Finalize can be applied to constant controlled objects. We could go further (though we could mark part of it as redundant) to say: ... or if it is an access-to-variable value that designates a constant object and it did not originate from an attribute_ reference applied to a{n aliased} variable view of [the] {a controlled or immutably limited} object. Would either of these rewordings at least partially satisfy your concerns? **************************************************************** From: Erhard Ploedereder Sent: Sunday, February 17, 2008 10:35 AM The second version (with the explicit mention of controlled and immutably limited) does address my concern about the generality of the previous version. It certainly bounds which constants are not constants. Is Controlled here an innocent bystander caught by accident or does anybody really believe that Adjust should be allowed to squirrel away access-to-variable onto a controlled non-limited object? (An honest question, not rethorical.) If innocent bystander, you could reduce it to the limited case. "Limited" doesn't even raise a contract issue, since generic matching on "limited" is an "if and only if"-match, so the body knows about limitedness of the actual. For Controlled, nothing is known about the actual from the formal, so this might cause contract issues. I have not thought that one through. I certainly can live with limited constants that are not constants. In the end, I have to, due to upward compatibility issues. ------- I would have suggested something identical for an obsolesced concept of constant limited objects, by saying that they are just like all other constants, except that they are treated as variables under an access-to-variable view per 13-9-1(13), and hence changes are well defined. Maybe I'll write it up, just for comparision. **************************************************************** From: Tucker Taft Sent: Sunday, February 17, 2008 12:15 PM I don't think there is any contract model issue, because for a non-controlled limited type, you get the aliased variable view inside the full type definition, and for a controlled type, you get the aliased variable view inside the Initialize and Adjust procedures (and Finalize of course, though that is a bit late to do much damage). As far as controlled being a "bystander" here, I don't really think so. One of the reasons to make a type controlled is so that internal pointers can be kept up to date across assignment. I think the circularly-linked list is a reasonable example. I've included an Initialize and Adjust implementation below. At any given time, there are two pointers that provide a variable view of the header node, even for a constant object. Although I didn't show it, it might be reasonable to retain some statistics in the header about number of references, total links followed to find desired element, etc. Here is an example of declaring a constant: C : constant Circularly_Linked_List := Build_List(X, Y, Z); Sample implementation follows. --------------------- generic type Element_Type is private; package Circularly_Linked_Lists is type Circularly_Linked_List is private; procedure Append( CLL : in out Circularly_Linked_List; Elem : Element_Type); ... private type CLL_Ptr is access all Circularly_Linked_List'Class; type Circularly_Linked_List is new Ada.Finalization.Controlled with record Next, Prev : CLL_Ptr; end record; procedure Initialize(CLL : in out Circularly_Linked_List); procedure Adjust(CLL : in out Circularly_Linked_List); procedure Finalize(CLL : in out Circularly_Linked_List); type Circularly_Linked_Node is new Circularly_Linked_List with record Payload : Element_Type; end record; ... procedure Initialize(CLL : in out Circularly_Linked_List) is begin CLL.Next := CLL'Unchecked_Access; CLL.Prev := CLL'Unchecked_Access; end Initialize; procedure Append( CLL : in out Circularly_Linked_List; Elem : Element_Type) is -- Add to end of list Old_Last : constant CLL_Ptr := CLL.Prev; begin CLL.Prev := new Circularly_Linked_Node'( Ada.Finalization.Controlled with Next => Old_Last.Next, Prev => Old_Last, Payload => Elem); Old_Last.Next := CLL.Prev; end Append; procedure Adjust(CLL : in out Circularly_Linked_List) is -- Do a deep copy of the circularly-linked list Old_First : constant CLL_Ptr := CLL.Next; Old_Header : constant CLL_Ptr := Old_First.Prev; New_Header : constant CLL_Ptr := CLL'Unchecked_Access; begin if Old_First = Old_Header then -- List is empty CLL.Next := New_Header; CLL.Prev := New_Header; else -- Copy each node with a payload declare Old_Node : CLL_Ptr := Old_First; Prev_Node : CLL_Ptr := New_Header; begin while Old_Node /= Old_Header loop declare -- Copy the old node, and point back to the -- new previous node New_Node : constant CLL_Ptr := new Circularly_Linked_Node'( Ada.Finalization.Controlled with Next => null, Prev => Prev_Node, Payload => Circularly_Linked_Node( Old_Node.all).Payload); begin -- Link into the previous node Prev_Node.Next := New_Node; -- Advance the pointers through -- the new and old list Prev_Node := New_Node; Old_Node := Old_Node.Next; end; end loop; -- Hook the last new node -- into the new header Prev_Node.Next := New_Header; New_Header.Prev := Prev_Node; end; end if; end Adjust; **************************************************************** From: Robert I. Eachus Sent: Sunday, February 17, 2008 2:32 PM > Let's agree that we disagree. But for the sake of others, let's describe our > positions in simple ways. Here is my version. > I am trying to save the notion that objects declared to be constants are > indeed constants and that this asserted invariance really holds. `When I use a word,' Humpty Dumpty said, in rather a scornful tone, `it means just what I choose it to mean -- neither more nor less.' `The question is,' said Alice, `whether you can make words mean so many different things.' `The question is,' said Humpty Dumpty, `which is to be master -- that's all.' -- Lewis Carroll in Through the Looking Glass This is one of the most quoted literary paragraphs in legal jurisprudence, and with good reason. Erhard wishes for the word constant to mean what he wants it to mean, not what it has always meant in Ada. What Erhard would apparently prefer is that declaring an object to be constant should override the implementation provided as part of the declaration of the type. In Ada 83, Ada 95, and Ada 2006, the effect of being a constant means lots of things--but it has never meant what Erhard wants. Perhaps the best way to put it is this. The defined behavior of a type is pervasive. If you change the definition of a type, the new behavior affects all object of that type, wherever declared. If that requires recompiling the world? So be it. However, the effect of declaring an object of a type to be constant is local to that object. A declaration cannot be rejected simply because the object is declared to be a constant. The Ada dependency rules force this behavior. So, we are either left with requiring all object declarations to support Erhard's view of how constant object should behave--whether or not there are ever any constant objects of the type--or the constant view is entirely local, and declarations elsewhere, including of Initialize and Adjust, can trump that constant view internally. I don't know why we are even having this discussion, let alone why it has gone on so long. 6.4.1(5) requires: "If the mode is in out or out, the actual shall be a name that denotes a variable." Seems clear. Yet, Initialize, Finalize, and Adjust all take an in out parameter. We can either depend on magic hand-waving, or admit that these procedures see a variable view of the object. Similarly operations on Storage_Pools (13.11) and Suspension_Objects (D.10) require in out parameters. Does this mean that you can't declare (potentially useless) constant objects of all these types? No. It means that constantness belongs to a view or a name, and affects only the viewer. It is not a property of the underlying object or type. For example, when a function returns a value, the value returned is often not constant within (the body of) the function. What is returned is a constant view of an object. **************************************************************** From: Jean-Pierre Rosen Sent: Monday, February 18, 2008 5:43 AM > [...], but I refuse to change the existing notion of a > constant object as a truly invariant object. Sorry, but that was not the case in Ada 83. A constant object could have an access component, and part of its state contained into a heap object that could be changed at will. The only difference with the case at hand is that we have now pointers to the same structure in place of pointers into an external structure, but it does not make much of a difference as far as theory of constantness is concerned. **************************************************************** From: Robert Dewar Sent: Monday, February 18, 2008 4:08 PM > The only difference with the case at hand is that we have now pointers > to the same structure in place of pointers into an external structure, > but it does not make much of a difference as far as theory of > constantness is concerned. I disagree, there is a huge difference between a constant object that may have pointers and one that can be modified. After all, in the sense you are trying to extend things to: X : constant Integer := 5; might not be constant because 5 could be the index into an array whose element #5 could be changed. Constantness is not about the conceptual value in this sense, it is about the value in the narrow Ada sense, and in this sense Ada 83 definitely has constants be real constants. **************************************************************** From: Tucker Taft Sent: Monday, February 18, 2008 4:22 PM I think Jean-Pierre is talking about private types. When a client of a private-type abstraction declares an object "constant" they are really only restricting themselves to passing the object to operations that have IN mode. They are in no way ensuring that the logical state of the object is unchanging. **************************************************************** From: Robert Dewar Sent: Monday, February 18, 2008 4:51 PM Certainly that's true, and as my example points out, this has nothing to do with access values per se! **************************************************************** From: Randy Brukardt Sent: Monday, February 18, 2008 6:06 PM > The second version (with the explicit mention of controlled and > immutably limited) does address my concern about the generality > of the previous version. It certainly bounds which constants > are not constants. Right, and that is fine. I don't much care about non-limited non-controlled types (it's unlikely that an ADT would be constructed as one of these anyway). > Is Controlled here an innocent bystander caught by accident or > does anybody really believe that Adjust should be allowed to > squirrel away access-to-variable onto a controlled non-limited > object? (An honest question, not rhetorical.) I think it is fundamental to the model of controlled types given by Ada. Claw works this way, and Tucker gives a reasonable example that is similar. What I'm objecting to is having an ADT becoming erroneous (or raising an exception for no obvious reason) if the user sticks "constant" on an object declaration. That's a hazard to the safe operation of the ADT, and similarly it is a maintenance hazard for fixes to the ADT (if anyone anywhere has declared a constant of the ADT type, reasonable maintenance could make that usage erroneous). This is an especial problem because style guides and compilers both have been recommending for years that objects be declared constant if possible (that is, if there is no usage that would prevent that declaration). It should not be the case that following the recommendation of a style guide (or a compiler message) makes a program unstable! In practice, I don't mind limiting the rule to covering just types that are immutably limited or controlled. It seems reasonable enough that if you want to build a bullet-proof ADT, you have to make the ADT type controlled (many factors tend to push you in that direction anyway). It's just another case where only tagged types work "right". The only acceptable solutions from the ADT perspective is either to work "right" or to be statically detected (presumably by adding it to the contract). Tucker's latest proposed wording essentially does both - describes what it means to do it "right" - and essentially adds it to the contract (if it is visibly controlled, it has to work right). Thus, if you can live with Tucker's proposed wording, that would be fine with me as well. ... > I certainly can live with limited constants that are not constants. In the > end, I have to, due to upward compatibility issues. In the general case, it doesn't even make sense for limited (true) constants to exist, as they may contain a task. As an active component, it surely is changing the state of the object as it runs. (I realize many runtimes make the task objects indirect, so it reduces to the access-in-an-object case. But it is still logically changing.) We may have made a mistake allowing such constants in Ada, but we made that mistake by popular demand (to make limited types work more like non-limited ones). And it seems too late to say "No, we didn't really mean it." And doing that wouldn't fix the issues with non-limited controlled types anyway, so it doesn't seem a productive path to visit. **************************************************************** From: Randy Brukardt Sent: Monday, February 18, 2008 6:12 PM ... > I think Jean-Pierre is talking about private > types. When a client of a private-type > abstraction declares an object "constant" they > are really only restricting themselves to > passing the object to operations that have IN > mode. And preventing the object from being the target of an assignment statement, if the type is non-limited. > They are in no way ensuring that the > logical state of the object is unchanging. Right. These can be powerful reasons for making an object constant, having nothing whatsoever to do with whether the logical state can be changed. **************************************************************** From: Jean-Pierre Rosen Sent: Tuesday, February 19, 2008 1:21 AM > Constantness is not about the conceptual > value in this sense, it is about the value in the narrow Ada sense, > and in this sense Ada 83 definitely has constants be real constants. I was specifically answering to Erhardt's view that "constants should be constants". Conceptually, and for private types. And although I have a lot of sympathy for this view, I was noting that it was already not true in Ada 83. **************************************************************** From: Robert Dewar Sent: Tuesday, February 19, 2008 2:10 PM But as I point out, in your conceptual sense, it could NEVER be true, so it is a bit meaningless to adopt this conceptual sense. **************************************************************** From: Erhard Ploederder Sent: Monday, November 24, 2008 3:54 PM I had the homework to create a NOTE about immutable constants. Here goes: ----------------------------- Add after 3.3(25): The value of a constant object cannot be changed after its initialization, unless the object has a controlled part or a part that is limited at the point of its declaration (see 7.5 and 7.6). ----- A bit of rationale: I left out the finalization bit, since for the types in question, finalization can only be trivial. The terminology on "controlled" parallels the verbage of 7.6 (10/2). If it is wrong here, it is likely to be wrong there, too. For limited, I needed to account for constant objects whose limited part became non-limited for crucial moments. My first version read: The value of a constant object cannot be changed after its initialization, unless the object has a controlled part or limited part (see 7.5 and 7.6). This looked nice and simple, except for a "becomes unlimited"-situation. Maybe, as a user note, it would suffice regardless. My second version was: The value of a constant object cannot be changed after its initialization, unless the object has a controlled part or limited part at the point of its declaration (see 7.5 and 7.6). This causes a contract question on "controlled", which could hide behind "private". So, I ended up with the above, which is as good or bad as 7.6 (10/2) wrt the "controlled behind private" argument. The text of this Note could also be used in lieu of the deleted [The value of a constant object cannot be changed between its initialization and its finalization, whereas the value of a variable object can be changed.] if one wanted to. In that case, it should continue: In all other cases, the value of an object can be changed. **************************************************************** From: Randy Brukardt Sent: Monday, November 24, 2008 4:17 PM > The terminology on "controlled" parallels the verbage of 7.6 (10/2). > If it is wrong here, it is likely to be wrong there, too. For limited, > I needed to account for constant objects whose limited part became > non-limited for crucial moments. Any particular reason that you did not use "immutably limited" in this wording, as is done in both parts of the new normative wording? This wording is actually slightly broader than needed, since it seems to include a type which is limited by having a limited component (not necessarily immutably limited). We used to have a whole bunch of different ways of saying "immutably limited", but supposedly we've changed them all to use the new term. "Immutably limited" is (already) used in 7 subclauses (and the index) and roughly 15 occurrences in the revised AARM. It's a term that should quickly become familiar -- so I don't think there is any good reason to avoid it. I suppose that there is some value to explaining the rules in different terms, but then it is odd that you're leaning on the defined term for "controlled" and not for "immutably limited". If you're not going to worry about whether you include too much in the note, then I think I actually prefer your first wording: "The value of a constant object cannot be changed after its initialization, unless the object has a controlled part or limited part (see 7.5 and 7.6)." because this is still a true statement (there are some objects with limited parts that cannot be changed after initialization, but there aren't any objects that can be changed that are not covered by the "unless"). The only extra cases covered by the "unless" that don't have to be covered involve using limited private types that have full types that aren't limited. And writing code that assumes that a limited private type is implemented by something non-limited (and thus gets covered by this note) sounds pretty dubious to me -- it makes the usage depend on the actual implementation of the private type. So for actual use, the simpler version probably is a good enough rule of thumb to remember. **************************************************************** From: Bob Duff Sent: Monday, November 24, 2008 4:33 PM > "The value of a constant object cannot be changed after its > initialization, unless the object has a controlled part or limited part (see 7.5 > and 7.6)." Seems to imply that if it has a "controlled part or limited part", it can be changed willy-nilly. And "controlled part or limited part" = "controlled or limited part". So how about: "The value of a constant object cannot be changed after its initialization, except in some cases where the object has a controlled or limited part (see 7.5 and 7.6)." **************************************************************** From: Erhard Ploederder Sent: Tuesday, November 25, 2008 8:25 PM Randy asked, why I didn't use "immutably limited". Mainly, because I used the Springer book rather than the AARM to leaf through definitions. So, no technical reason. It merely hasn't sunk in as new terminology with me and so I would not have felt comfortable. Reading the modified 13.9.1(13) in the AI, I guess it would be ok to add the "immutably" to the text. The "part"-part came from someone's observation at the meeting that it sufficed to have a subcomponent of the ilk to make the enclosing constant object mutable (merely by the fact that the subcomponent could be altered). That made sense to me. Bob's observations were good ones, too. How about: The value of a constant object cannot be changed after its initialization, except in some cases where the object has a controlled or immutably limited part (see 7.5, 7.6 and 13.9.1). ****************************************************************