!standard 4.5.2(9.7/2) 09-10-12 AI05-0123-1/07 !standard 4.5.2(14) !standard 4.5.2(15) !standard 4.5.2(24) !standard 8.5.4(8) !class Amendment 08-10-17 !status Amendment 201Z 09-06-29 !status WG9 Approved 09-11-05 !status ARG Approved 7-0-0 09-06-13 !status work item 08-10-17 !status received 08-09-03 !priority Low !difficulty Hard !subject Composability of equality !summary Primitive "=" operators for tagged and untagged records compose alike. !problem A user-defined primitive "=" operator for a tagged type "composes" in the sense that the predefined equality operator for an enclosing type which has one or more components of the given tagged type is defined in terms of the user-defined "=" operator; the overridden predefined "=" operator for the tagged type does not reemerge in this case. This is not true for untagged types. Tagged types were new in Ada95; thus the introduction of this rule in Ada95 was not an incompatible change. This rule was not introduced for untagged types because that would have been an incompatible change. It has since been decided that, at least for untagged records, this was probably a mistake. Typically the feared "incompatible change" results in the correction of a bug. !proposal If a nonlimited composite type contains a component of an untagged record type with a user-defined primitive "=" operator, then the predefined equality operator for the composite type is defined in terms of the user-defined equality operator of the record type in the same way as if the record type were tagged. !wording Add after 4.5.2(9.7/2): The explicit declaration of a primitive equality operator of an untagged record type, if its profile is type conformant with that of the corresponding predefined equality operator, shall occur before the type is frozen. If the untagged record type has a nonlimited partial view, then the declaration shall occur in the visible part of the enclosing package. In addition to the places where Legality Rules normally apply (see 12.3), this rule applies also in the private part of an instance of a generic unit. Replace 4.5.2(14): For a type extension, predefined equality is defined in terms of the primitive [(possibly user-defined)] equals operator of the parent type and of any tagged components of the extension part, and predefined equality for any other components not inherited from the parent type. with For a type extension, predefined equality is defined in terms of the primitive [(possibly user-defined)] equals operator for the parent type and for any components that have a record type in the extension part, and predefined equality for any other components not inherited from the parent type. For a derived type whose parent is an untagged record type, predefined equality is defined in terms of the primitive (possibly user-defined) equals operator of the parent type. Replace 4.5.2(15): For a private type, if its full type is tagged, predefined equality is defined in terms of the primitive equals operator of the full type; if the full type is untagged, predefined equality for the private type is that of its full type. with For a private type, if its full type is a record type, predefined equality is defined in terms of the primitive equals operator of the full type; otherwise, predefined equality for the private type is that of its full type. Replace 4.5.2(24): * Otherwise, the result is defined in terms of the primitive equals operator for any matching tagged components, and the predefined equals for any matching untagged components. with * Otherwise, the result is defined in terms of the primitive equals operator for any matching components that are records, and the predefined equals for any other matching components. If the primitive equals operator for an untagged record type is abstract, then Program_Error is raised at the point of any (implicit) call to that abstract subprogram. AARM Reason: An explicit call to an abstract subprogram is illegal. This rule is needed in order to define the effect of an implicit call such as a call that is part of the predefined equality operation for an enclosing composite type that has a component of an untagged record type that has an abstract primitive equals operator. For tagged types, an abstract primitive equals operator is only allowed for an abstract type, and abstract types cannot be components, so this case does not occur. In AARM 4.5.2(32.a.1/1), replace "then either the full type must be tagged" with "then either the full type must be a record type" . Append after 8.5.4(8): A corresponding rule applies to a call on a renaming of a predefined equality operator for an untagged record type. !discussion We do not include arrays because of the predefined lexicographic ordering operators for discrete array types. Since these rules change what "=" operator is used within a generic, we could have a situation where "=" is user-defined but the predefined "<=" reemerges. This would be bad as the two operators would no longer have the correspondence that the creator of the abstraction intended. Note that arrays with a component type that is a record with a user-defined "=" will compose "=" correctly (the predefined array "=" will use the user-defined "=" on the components); this just means that in the case of arrays with user-defined "=", the "=" will not compose. (It is likely that the "=" exists simply because the components did not compose in earlier versions of Ada, so that this will make no difference.) --- It would be pretty weird if the directly called equality didn't agree with the composed equality. For example, a call to the "=" operator for an untagged record type R ought to yield the same answer as a corresponding call to the predefined "=" operator for a one-field record type whose one component is of type R. If that is not possible (or the semantics would change from Ada 2005 for a direct call on "="), then you should get a compile-time or run-time error from a direct call on "=". We handle some corner cases as follows: 1) The "composed" equality operator for a type with a component whose primitive "=" operator is abstract is defined to raise Program_Error. This is only possible for a component of an untagged record type. We made this a run-time error as making the type declaration illegal would make programs illegal even if they didn't call "=". Making any call on "=" illegal in this case would cause a significant generic contract issue: we would have to assume the worst in a generic body for any formal private type -- meaning that "=" could never be used there. That's obviously too incompatible to seriously consider. 2) A user-defined primitive "=" operator for an untagged record type may not be declared after the freezing point of the type, nor may it be declared in the private part of a package spec if (a full or partial view of) the record type is declared in the visible part. This preserves the principle that "=" always calls the same body no matter what view of the type is available. Otherwise the implementation of "=" called could depend on the view of the type, which would be a nightmare for composition. 3) Referencing a predefined "=" operator in a renames before overriding the "=" operator is treated as having "squirreling" semantics (see AARM 8.5.4(8.g)) !example The order of declarations does not matter when determining what body for "=" is called. Consider the following example: package Pkg is type Rec is null record; function "=" (L, R) return Boolean is abstract; type Drec is new Rec; -- inherits abstract "=" type Vec is array (Boolean) of Drec; function "=" (L, R : Drec) return Boolean; end Pkg; Vec's predefined "=" operator is defined in terms of that of Drec, which is defined in terms of the explicitly defined "=" operator. Thus, "=" for Vec does not necessarily raise Program_Error. Consider the following example: package P is type T is limited private; package Inner is type Rec is record X : T; end record; end Inner; function "=" (L, R : T) return Boolean is abstract; private type T is null record; end P; package body P is package body Inner is X, Y : Rec; Flag : Boolean := X = Y; end Inner; end P; The elaboration of the declaration of Flag raises Program_Error. !corrigendum 4.5.2(9.7/2) @dinsa @xbullet @dinst The explicit declaration of a primitive equality operator of an untagged record type, if its profile is type conformant with that of the corresponding predefined equality operator, shall occur before the type is frozen. If the untagged record type has a nonlimited partial view, then the declaration shall occur in the visible part of the enclosing package. In addition to the places where Legality Rules normally apply (see 12.3), this rule applies also in the private part of an instance of a generic unit. !corrigendum 4.5.2(14) @drepl For a type extension, predefined equality is defined in terms of the primitive (possibly user-defined) equals operator of the parent type and of any tagged components of the extension part, and predefined equality for any other components not inherited from the parent type. @dby For a type extension, predefined equality is defined in terms of the primitive (possibly user-defined) equals operator for the parent type and for any components that have a record type in the extension part, and predefined equality for any other components not inherited from the parent type. For a derived type whose parent is an untagged record type, predefined equality is defined in terms of the primitive (possibly user-defined) equals operator of the parent type. !corrigendum 4.5.2(15) @drepl For a private type, if its full type is tagged, predefined equality is defined in terms of the primitive equals operator of the full type; if the full type is untagged, predefined equality for the private type is that of its full type. @dby For a private type, if its full type is a record type, predefined equality is defined in terms of the primitive equals operator of the full type; otherwise, predefined equality for the private type is that of its full type. !corrigendum 4.5.2(24) @drepl @xbullet @dby @xbullet If the primitive equals operator for an untagged record type is abstract, then Program_Error is raised at the point of any (implicit) call to that abstract subprogram. !corrigendum 8.5.4(8) @drepl For a call on a renaming of a dispatching subprogram that is overridden, if the overriding occurred before the renaming, then the body executed is that of the overriding declaration, even if the overriding declaration is not visible at the place of the renaming; otherwise, the inherited or predefined subprogram is called. @dby For a call on a renaming of a dispatching subprogram that is overridden, if the overriding occurred before the renaming, then the body executed is that of the overriding declaration, even if the overriding declaration is not visible at the place of the renaming; otherwise, the inherited or predefined subprogram is called. A corresponding rule applies to a call on a renaming of a predefined equality operator for an untagged record type. !ACATS test ACATS C-Tests should be constructed to check that composition is implemented as required. ACATS B-Tests should be constructed to check that the legality rules are properly implemented. Any existing tests requiring reemergence should be repaired/withdrawn. !appendix From: John Barnes Sent: Wednesday, September 3, 2008 1:47 AM I have a request from members of the BSI Ada panel to consider the attached thoughts on composability of equality. Can we put it on the agenda for the next meeting please? !standard 08-09-03 AI05-nnnnn-1/01 !class Amendment !status work item !status received !priority Medium !difficulty Easy !subject Composability of equality !summary A pragma Composable_Equality is proposed to indicate that equality for the type is to be composable. !problem Consider the following example type My_Float is new Float; function "=" (L, R : My_Float) return Boolean is begin if L > 0.99 * R and then L < 1.01 * R then return True; -- Near enough else return False; end if; end "="; type Position is record Latitude : My_Float; Longitude : My_Float; end record; Position1 : Position := (90.00, 90.00); Position2 : Position := (90.01, 89.99); begin if Position1 = Position2 then –- Uses original predefined equals -- for components The user might expect that the type Position would use the newly defined equality for its components. However, predefined equality reemerges unless the type is tagged. The user therefore has to remember to redefine equality for the type Position as well. This is a potential source of errors and confusion. !proposal Introduce a pragma Composable_Equality(Type_Name) to indicate that the same composability rules that apply to tagged types also apply to the type Type_Name. !wording tbd !discussion The fact that all types do not compose for equality is surprising. However, although it was changed for tagged types when they were introduced in Ada 95, it was not done for nontagged types for reasons of backward compatibility. One possible workaround is to declare the types to be tagged but this is not always appropriate. Are there similar problems with user-defined operations in generics and what should be done about it? !example !ACATS test !appendix **************************************************************** From: Robert Dewar Sent: Wednesday, September 3, 2008 9:09 AM Can we have a reminder of the rationale behind NOT making this commposable in the first place? **************************************************************** From: Robert Dewar Sent: Wednesday, September 3, 2008 9:09 AM I would also allow this as a configuration pragma without a type name, meaning that it applies to all types (really quite a language change, but that's really the intent here). **************************************************************** From: Pascal Leroy Sent: Friday, September 5, 2008 12:36 AM I find the idea of so-called composable equality for elementary types (especially for floats, as shown in the example) extremely dangerous. There is no way that the behaviour of any generic (notably the ones in Ada.Numerics) can be predicted. I am pretty sure that the IBM implementation of the elementary functions and the matrix algorithms would fall over dead with the "=" shown in the example (or at least return random results -- think of the computation of eigenvalues as an example). Furthermore, many of these generics expect some consistency between "=" and the other relational operators. Presumably redefining "=" doesn't affect "<=", which is unlikely to be what the author of the generic intended. Overall, I think the proposal only makes sense for (untagged) records. And I am not too excited by the prospect of a configuration pragma: it seems to me that users should give careful thought to the impact of using this pragma, they should not just slap it in the configuration and forget about it. Incidentally, most (all?) of the cases where I have seen people want to use fuzzy equality for floats were situations where they were too lazy/incompetent to do proper error analysis. The fudge factors 0.99 and 1.01 look highly suspicious to me, and are likely to be bugs waiting to happen. **************************************************************** From: Robert Dewar Sent: Wednesday, September 17, 2008 5:24 PM > Incidentally, most (all?) of the cases where I have seen people want > to use fuzzy equality for floats were situations where they were too > lazy/incompetent to do proper error analysis. The fudge factors 0.99 > and 1.01 look highly suspicious to me, and are likely to be bugs > waiting to happen. fuzzy equality for floats is a bad idea period! I agree entirely that this is a dubious idea patching over incompetent programming. Many coding standards forbid equality on floats. That's also misguided. Equality is well defined for floats in IEEE arithmetic, and many algorithms legitimately use equality, e.g. for abosolute conversion in algorithms where such conversion is expected. **************************************************************** From: John Barnes Sent: Monday, October 20, 2008 11:21 AM Can I bring up the equality business again? Much of the traffic on this topic has been about the problems of fuzzy floats. The original example in the proposed AI was therefore unfortunate by confusing the issue. It was about the general desire to control composability and not meant to be about fooling around with floating equality per se. Below is another example not involving floats. It has two types Doctor_Type and Appointment_Type. Both have equality redefined to ignore certain components of the type. It then introduces a type Appointment hoping that this type would use the redefined equality for the components but it doesn't. I think it was Pascal that suggested that the pragma should only be applicable to nontagged records. Incidentally I believe the RM says that all predefined types are composable. Where is it? I seem to recall a discussion on this when doing Ada 95. Are they composable because they behave as if the proposed pragma has been applied (ie magic) or are they composable because the predefined equality has not been messed about with? --- with Ada.Calendar; use Ada.Calendar; with Ada.Text_IO; with Ada.Integer_Text_IO; procedure Compose is type Doctor_Type is record Name : string (1..20) := "Unknown "; Comments : string (1..20); end record; function "=" (Left, Right : Doctor_Type) return Boolean is begin if Left.Name = Right.Name then return True; else return False; end if; end "="; type Patient_Type is record Name : string (1..20) := "Unknown "; Comments : string (1..20); end record; function "=" (Left, Right : Patient_Type) return Boolean is begin if Left.Name = Right.Name then return True; else return False; end if; end "="; type Appointment_Type is record Doctor : Doctor_Type; Patient : Patient_Type; Date : Time; end record; Appointment_1 : constant Appointment_Type := ( Doctor => (Name => "Oliver Cousins ", Comments => "Neurologist "), Patient => (Name => "Amy Cole ", Comments => "Always late "), Date => Time_Of (Year => 1988, Month => 8, Day => 8)); Appointment_2 : constant Appointment_Type := ( Doctor => (Name => "Oliver Cousins ", Comments => "Nerve specialist "), Patient => (Name => "Amy Cole ", Comments => "Late again "), Date => Time_Of (Year => 1988, Month => 8, Day => 8)); begin Ada.Text_IO.Put_Line ("Doctors"); Ada.Text_IO.Put (Appointment_1.Doctor.Name); Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line ("and"); Ada.Text_IO.Put (Appointment_2.Doctor.Name); Ada.Text_IO.New_Line; Ada.Text_IO.Put ("are "); if Appointment_1.Doctor = Appointment_2.Doctor then Ada.Text_IO.Put_Line ("the same"); else Ada.Text_IO.Put_Line ("different"); end if; Ada.Text_IO.Put_Line ("Patients"); Ada.Text_IO.Put (Appointment_1.Patient.Name); Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line ("and"); Ada.Text_IO.Put (Appointment_2.Patient.Name); Ada.Text_IO.New_Line; Ada.Text_IO.Put ("are "); if Appointment_1.Patient = Appointment_2.Patient then Ada.Text_IO.Put_Line ("the same"); else Ada.Text_IO.Put_Line ("different"); end if; Ada.Text_IO.Put_Line ("Appointments"); Ada.Text_IO.Put (Appointment_1.Doctor.Name); Ada.Text_IO.Put (' '); Ada.Text_IO.Put (Appointment_1.Patient.Name); Ada.Text_IO.Put (' '); Ada.Integer_Text_IO.Put (Year (Appointment_1.Date)); Ada.Text_IO.Put (' '); Ada.Integer_Text_IO.Put (Month (Appointment_1.Date)); Ada.Text_IO.Put (' '); Ada.Integer_Text_IO.Put (Day (Appointment_1.Date)); Ada.Text_IO.New_Line; Ada.Text_IO.Put_Line ("and"); Ada.Text_IO.Put (Appointment_2.Doctor.Name); Ada.Text_IO.Put (' '); Ada.Text_IO.Put (Appointment_2.Patient.Name); Ada.Text_IO.Put (' '); Ada.Integer_Text_IO.Put (Year (Appointment_2.Date)); Ada.Text_IO.Put (' '); Ada.Integer_Text_IO.Put (Month (Appointment_2.Date)); Ada.Text_IO.Put (' '); Ada.Integer_Text_IO.Put (Day (Appointment_2.Date)); Ada.Text_IO.New_Line; Ada.Text_IO.Put ("are "); if Appointment_1 = Appointment_2 then Ada.Text_IO.Put_Line ("the same"); else Ada.Text_IO.Put_Line ("different"); end if; end Compose; **************************************************************** From: Tucker Taft Sent: Monday, October 20, 2008 11:34 AM I would be in favor of composable equality for all record types. Currently only tagged record types compose, and I think that we were just running scared of teeny incompatibilities in Ada 9x when we resisted extending that to cover all record types. I agree with Pascal that making user-defined operators on numeric types "compose" is asking for trouble, but I don't see the problem for record types. Upward compatibility would argue for a pragma, I suppose, but I would consider just doing it by fiat, and let the chips fall where they may. I really can't imagine a case where you wouldn't want record-type equality to compose, but I suppose I am willing to hear about one. **************************************************************** From: Edmond Schonberg Sent: Monday, October 20, 2008 11:40 AM But then why exclude it for array types? **************************************************************** From: Robert Dewar Sent: Monday, October 20, 2008 11:46 AM > I really can't imagine a case > where you wouldn't want record-type equality to compose, but I suppose > I am willing to hear about one. It's less an issue of an example that has some deliberate use of this misfeature, than worrying about mysterious incompatibilities for existing code. Ed, how about we instrument GNAT to see how many cases in our test suite would be affected by such a change **************************************************************** From: Tucker Taft Sent: Monday, October 20, 2008 11:56 AM Array types are more difficult in my view. You can convert between unrelated array types, and if the equality properties have to do with something other than the component type, that might imply some special situations with slices and/or sliding. I also worry that some array types have operations besides equality (<, >, and, or, etc.). I am not excited about trying to define how those compose. Record types have at most one predefined operator that the user can override explicitly (yes, I know about "/=" ;-), and that makes them much simpler to deal with in my view. **************************************************************** From: Randy Brukardt Sent: Monday, October 20, 2008 12:00 PM > But then why exclude it for array types? The problem (as I understand it) is that it is nearly impossible to write a correct generic if you cannot trust the relationships between the various relational operators. And there is no composition of other operators. That's not a problem for record types, because they don't have any other relational operators other than equality. But (some) array types do have the other ordering operators; since those don't compose, the same problem would occur for them. I suppose we could try to fix that problem by trying to figure out how composition of the other predefined operators ought to work, but it surely isn't as obvious as "=" is. **************************************************************** From: Edmond Schonberg Sent: Monday, October 20, 2008 12:18 PM > Ed, how about we instrument GNAT to see how many cases in our test > suite would be affected by such a change We could check on all uses of predefined equality on records for which some component has a user-defined equality. Is that what you have in mind? **************************************************************** From: Robert Dewar Sent: Monday, October 20, 2008 12:28 PM yes, these are the cases where a change would make a difference **************************************************************** From: Tucker Taft Sent: Monday, October 20, 2008 12:37 PM That would certainly be some relevant data. **************************************************************** From: Edmond Schonberg Sent: Monday, October 20, 2008 4:54 PM Preliminary results: out of some 11719 relevant tests in the suite, there are 23 that perform equality on a record that has some component for which there is an explicit equality. Most of them are records that contain an unbounded_string or an address. I'll look at some of these to see whether proper composition would affect the results. **************************************************************** From: Tucker Taft Sent: Monday, October 20, 2008 5:05 PM But both of these types are required to have composable equality by the language definition (see RM 4.5.2(32.1/1)). Any other cases where the types aren't language defined types? **************************************************************** From: Randy Brukardt Sent: Monday, October 20, 2008 5:07 PM For nonlimited language-defined types, the language requires that they compose properly (or act like it). See 4.5.2(31.1/1). So proper composition should not affect the results of either of those types (if it does, your compiler has a bug). A component would have to be a user-defined type in order for it to matter. **************************************************************** From: Edmond Schonberg Sent: Monday, October 20, 2008 5:40 PM > But both of these types are required to have composable equality by > the language definition (see RM 4.5.2(32.1/1)). And indeed they compose properly. no problem here. My mod to the compiler did not special-case these types. > Any other cases where the types aren't language defined types? There are 6 tests with user-defined types, that I have to examine more closely. **************************************************************** From: Edmond Schonberg Sent: Monday, October 20, 2008 8:52 PM OK, there are 5 tests with equality on untagged record types that have components with user-defined equality. Of these: One is a query from a customer (many years ago) who was puzzled that equality did not compose. One of them is in GNAT code, and one of the operands is a constant that represents the null value for the type. This works properly in this case but it's slightly suspicious, and would in fact be better if equality composed. One of them is in the expanded code for a generated equality routine with a subcomponent with user-defined equality. Two of them are in Inspector code, and I'm sure they are correct :-)! Conclusion from this small sample: having equality compose would be harmless, and of course clearer conceptually. **************************************************************** From: Randy Brukardt Sent: Monday, October 20, 2008 9:14 PM No ACATS tests? They wouldn't prove anything other than whether compilers are actually doing this all the same way (no test would mean no certainty of portability in this area). **************************************************************** From: Robert Dewar Sent: Monday, October 20, 2008 11:10 PM Maybe we could just be extreme and declare that it was simply a typo that equality did not compose and we forgot to fix it in Ada 95 and Ada 2005 :-) **************************************************************** From: Randy Brukardt Sent: Monday, October 20, 2008 11:49 PM That's tempting, but I think that the subtle incompatibility involved is best being associated with a major version change to the language. I recall someone else making the point that users are not likely to be surprised about incompatibilities when moving between major versions (like Ada 95 to Ada 2005), but are more likely to not be expecting changes when moving from GNATPro 6.2 to 6.2.1 (or some similar minor compiler change). The problem here is while we're pretty sure the change will fix more bugs than it introduces, there is likely to be that one big project that somehow managed to depend on this behavior. I suspect that it would be better to tie the change to Ada 2005 (if we're even that bold) or Ada 2014 (if we're not); that would have the advantage that projects with problems could at least regress to the Ada 95 semantics (presuming that most compilers have a way to get that). **************************************************************** From: Robert Dewar Sent: Tuesday, October 21, 2008 8:17 AM Actually my view is that it is unacceptable to regard major version changes as a source of incompatibilities. If we think of this as an incompatibility that is only acceptable for a major version change, then it is not acceptable for that either. The Ada 2005 design introduced serious incompatibilities that are still causing trouble, and still acting as an impediment to adoption of Ada 2005. Users *ARE* surprised by significant incompatibilities between versions, and a *MAJOR* inducement to moving to a new version of the language is to guarantee that there are no such cases. Ada 2015 is very unlikely to have enough goodies to induce change ten years out to cancel out worries about incompatibilites. To me there are only two acceptable choices a) decide this is a fix, and that therefore it is not only acceptable but desirable to retrofit it to old versions. An implementor worried about the incompatibility issue can introduce a switch to maintain the old "wrong" behavior. b) decide that this is an incoimpatible change, in which case it is not acceptable unless triggered by a pragma, which should either be useable as a global configuration pragma, or be applicable to a specified type. **************************************************************** From: Jean-Pierre Rosen Sent: Tuesday, October 21, 2008 8:59 AM Or maybe: c) decide that this is an incompatible but desirable change, with a pragma to keep the *old* behaviour, for the rare cases where there is a serious compatibility issue. **************************************************************** From: Robert Dewar Sent: Tuesday, October 21, 2008 9:06 AM I would use a pragma for the new behavior. In new code wanting to use this feature, chucking in a pragma is no sweat, but telling people they have to modify existing code because of an incompatibilty we consider significant seems undesirable. BTW what was the original motivation behind the rule? Given that it does not work for fpt to do block compares, the whole thing seems silly to me. **************************************************************** From: Tucker Taft Sent: Tuesday, October 21, 2008 9:32 AM The decision in Ada 9X about tagged versus untagged was connected to a number of relatively obscure technical issues. In general, we required overriding of an operation of a tagged type to be subtype conformant with the inherited operation, whereas Ada 83 only required "type conformance" for overriding of operations of an untagged type, which meant that even the modes of the parameters could differ. The overriding of predefined operators obviously couldn't have mode mismatches, since operators only allow IN parameters, but you could have different subtypes at the least, though why you would want to override with a more restrictive subtype is hard to imagine. We wanted to treat operators the same way we were treating the operations inherited from the ancestor type for a formal derived type, and we wanted composability and the rules for reemergence in generic instances to match up consistently. If we step back now and see where the biggest "surprise" is, I think predefined record-type equality "reemerging" in generics, and not composing properly, is the one that remains. A surgical strike to eliminate that seems like a good idea in my mind, and leave the other differences between tagged and untagged types as they are. And I guess I agree with Robert, this ought to be something that can be back-fitted to existing compilers. Unlike the Ada 83 to Ada 95 transition, I see people "slipping" into Ada 2005 features on a more incremental basis. Containers are very attractive, and just getting them to compile either requires making various changes to them to get them back into Ada 95 acceptability, or starting to adopt at least some Ada 2005 features. I think in retrospect it *was* a mistake to distinguish between tagged and untagged record types as far as predefined equality, but it was hidden behind some bigger distinctions that I believe were *not* a mistake. A pragma is probably the right approach, but there is nothing saying that it couldn't be the "default" configuration to have the pragma already in place. Having a pragma that allows the user to specify either direction is probably safest, so that if you say nothing, you get the default behavior of the compiler (unless in ACATS mode of course where you get the standard behavior which at this point is no composition), and then you can specify either you want composition, or you rely on non-composability. **************************************************************** From: Tucker Taft Sent: Tuesday, October 21, 2008 10:01 AM By the way Robert, what have emerged as the biggest incompatibilities in Ada 2005, in AdaCore's experience? I presume the loss of by-reference function return is one. Others? **************************************************************** From: Robert Dewar Sent: Tuesday, October 21, 2008 10:30 AM > By the way Robert, what have emerged as the biggest incompatibilities > in Ada 2005, in AdaCore's experience? > I presume the loss of by-reference function return is one. Others? That's the really big one, can't remember any others that are causing continued grief, Bob? **************************************************************** From: Tucker Taft Sent: Tuesday, October 21, 2008 9:54 AM Gosh, I hate to say it, but this might argue for bringing back the idea of a "Features" pragma... ;-) The danger of having a single-purpose pragma for this is that if the compiler does *not* support the feature, you don't get an error, but instead just a warning that you have an unrecognized pragma. If we add a general pragma that the compiler *must* support, such as "Restrictions," and then require that it fail if it sees the name of a feature it doesn't recognize, we can give the user some comfort that if they are depending on the presence of some particular feature, they will get an error if the feature isn't supported. We could use "No_" and "" to indicate a requirement that the feature not be present, or that the feature be present. E.g.: pragma Features(No_Record_Equality_Composability); vs. pragma Features(Record_Equality_Composability); This pragma would cause an error if the feature name were not recognized. **************************************************************** From: Robert Dewar Sent: Tuesday, October 21, 2008 10:25 AM I think I would prefer pragma Composable_Equality (On | Off); **************************************************************** From: Joyce Tokar Sent: Tuesday, October 21, 2008 10:31 AM Why not update pragma Restrictions to achieve this goal? **************************************************************** From: Robert Dewar Sent: Tuesday, October 21, 2008 10:43 AM this really has nothing to do with a restriction, and I don't see a way of recasting it in that mode, do you? Joyce? **************************************************************** From: Tucker Taft Sent: Tuesday, October 21, 2008 11:05 PM Composable_Equality is going a bit too far, since I don't think we want to imply that we will do this for non-record types. You didn't comment on my point about recognizable pragmas -- that is we want the user to be sure the pragma isn't just being ignored. I also worry about a proliferation of special-purpose pragmas. Eventually you want to be able to have a single place where you define what is the set of features you are using. With a pragma like "Features" and the existing "Restrictions," we could have a pair of pragmas that pretty much define what is acceptable. If we define a collection of possibly overlapping feature/restriction-ish pragmas, really knowing what features are safely usable in a given program may be tricky. When combined with the notion of a Profile, we could get a nice triumvirate. **************************************************************** From: Joyce Tokar Sent: Tuesday, October 21, 2008 11:48 AM Essentially, this is what I was driving at with my question about pragma Restrictions --- and we know about restriction profile -- see Ravenscar. **************************************************************** From: Robert Dewar Sent: Tuesday, October 21, 2008 11:26 AM What are other features you have in mind for this pragma, a pragma with only one possibility seems silly to me. what existing pragams could be subsumed, also we want this pragma to be able to apply to a specific type, how would that fit into your scheme. I don't see any problem in proliferation, we have hundreds of pragmas already! **************************************************************** From: Tucker Taft Sent: Tuesday, October 21, 2008 12:00 PM I had imagined using a pragma Features for the various Ada 2005 features which might be provided "piecemeal." I suppose it would be most important for features that involve any kind of potential semantic inconsistency, such as the return-by-reference vs. build-in-place, but it could be useful as documentation for the set of features expected to be fully supported. The danger is that some compiler might accept the syntax for something like access-to-subprogram, but not support the full downward closure semantics. Or more obviously, whether null values can be passed to access parameters might not be at all obvious, because we have encouraged Ada 95 compilers to accept, and ignore, the "not null" syntax on access parameters. I can see the same thing effectively happening with, say "private with," where the compiler would be augmented to accept the syntax, but treat it as equivalent to simply "with." This is most tempting when trying to compile the container packages, where you might be able to get away with accepting the Ada 2005 syntax they use, but "punt" on the semantics where possible. I know I was tempted by that recently, because the latest GNAT sources use container packages, and I was trying to compile them with AdaMagic to host the gnat2scil on my laptop in the absence of a GNAT version for Mac OS X that could compile the inspector. **************************************************************** From: Edmond Schonberg Sent: Tuesday, October 21, 2008 12:06 PM >> Conclusion from this small sample: having equality compose would be >> harmless, and of course clearer conceptually. > > No ACATS tests? They wouldn't prove anything other than whether > compilers are actually doing this all the same way (no test would mean > no certainty of portability in this area). Indeed, no ACATS tests (either B or C) that have a comparison of untagged records with a component that has a user-defined equality. Given the state of this discussion, you might not want to rush and add this to the test objectives of ACATS 3X :-)! **************************************************************** From: Robert I. Eachus Sent: Tuesday, October 21, 2008 1:46 PM I think the idea of a pragma is best. That way with a wink an a nod, the default behavoir for compilers can be equality always composing in records. I also think that proper names for the pragmas should make it clear which one to use in your code if you are a belt and suspenders type.. (Only needed of course, if you define an equality operation for a type.) pragma Equality_Composes( For => Base_Type); --seems right, with -- no argument can be used as a configuration pragma. The other way is harder: pragma Equality_Does_Not_Compose(For => Base_Type); --seems wordy, and still not correct. pragma Predefined_Equality_Reemerges(For => Base_Type); --not as scary for the uninitiated... Any better suggestions? It would be nice to have something that looks appropriate for Boolean. **************************************************************** From: Randy Brukardt Sent: Wednesday, October 22, 2008 8:14 PM ... > BTW what was the original motivation behind the rule? Given that it > does not work for fpt to do block compares, the whole thing seems > silly to me. I was hoping to answer this question, since I was wondering about that myself. Tucker has answered why tagged types are different, but the more interesting question is why Ada 83 adopted non-composition in the first place. However, none of the on-line materials (such as the Ada 83 Rationale) say anything about equality at all (so the smaller question of composition isn't answered either). I could speculate (This issue was never really considered? a misguided attempt to reduce implementation complexity? A worry about consistency of relational operations?) but that doesn't do any good. So, unless John Goodenough has the answer in his huge stack of paper discussions [and finding it in there is not likely to be easy], I'm afraid the reason is lost to the mists of time. **************************************************************** From: Robert Dewar Sent: Wednesday, October 22, 2008 8:21 PM And as I noted, you can't in general do block compares because of float values *anyway*, so the whole thing seems bogus It is interesting that several of the cases we found in our test suite were in our internal code, and in all cases, the author was VERY surprised and we had latent bugs. I more and more think we should do the following a) fix the bug, for all versions of Ada b) note that implementors may wish to provide a pragma to rever to the old state of the language. We can suggest the form of this pragma, but I see no reason to have it be part of the formal definition of Ada. I don't believe there could be any real compatibility problems in practice, on the contrary I think it is likely this will fix bugs! **************************************************************** From: Tucker Taft Sent: Wednesday, October 22, 2008 9:35 PM You weren't allowed to redefine equality for non-limited types in Ada 83, so the problem didn't arise. Why weren't you allowed to redefine equality? I suspect because you weren't allowed to redefine assignment, and there is an implicit connection between equality and assignment. **************************************************************** From: Robert I. Eachus Sent: Wednesday, October 22, 2008 10:11 PM ... >So, unless John Goodenough has the answer in his huge stack of paper >discussions [and finding it in there is not likely to be easy], I'm >afraid the reason is lost to the mists of time. If you are asking about the non-composition of equality in records, that is certainly not lost. The intent was to allow comparison for records to be implemented as a bit for bit* comparison. The same holds true for arrays, at least for equality of arrays of boolean and enumeration types. I do remember lots of discussion prior to Ada 83 being finalized about equality for floating point in the presence of potentially unnormalized values. But that basically went away with the hardware that permitted it. Both the IEEE and DEC floating point formats have a single valid representation for every value--at least within types. But there was a lot of hardware around in the early eighties with non-IEEE formats, and some allowed mixing unnormalized and normalized values. The hardware did the floating point compares right--but the existence of an FP field in a record on such hardware meant you couldn't use a bitwise compare. The issues of implementing equality when there were unused or uninitialized bits in a record, didn't really emerge until after Ada 83 was in its final form. There was a similar implementation issue that showed up with the 80286. You could have pointers--excuse me access values--that designated the same object which were not bit for bit identical. I don't think this ever got resolved as such, since the 80386 encouraged, instead, the use of a 32-bit flat address space. (You could ask Alsys how their '286 compiler dealt with that. My guess is that the compiler only created one designation for a heap object, and other equivalent pointer values were treated as non-identical.) There was also an issue with the Motorola 860x0 family addresses. They were technically 36-bit addresses, and the 16 address spaces could point at different views of the same memory. However, AFAIK, this feature was only used during boot up. The boot PROM was usually given its own address space, then mapped above the RAM once virtual memory was activated. AFAIK no 680x0 OS ever supported more than 32-bit addressing. (I remember the 36-bit address details mostly because of changes between the 68020 and 68030 in this area. I updated the Stratus assembler to support 68030 chips, and at least half the work involved addressing modes never used once VM started.) * Of course, compilers were expected to optimize these compares to use byte, 16-bit, 32-bit, etc. compare instructions when possible. (Now, 64. 128. and soon 256-bit operations.) Early compilers, though, were doing good if they used string compare instructions for String. Dave Emery wrote a move for records in Ada that used 32-bit instructions (and Unchecked_Conversion! ;-) for all but a few bytes, because the compiler generated code was so slow in, I think, Verdix 4.1. Anyway when he showed the results to Verdix, they incorporated his code into the compiler. (After asking permission, and Dave publishing the code in a paper to handle the rights issues. ;-) **************************************************************** From: Robert Dewar Sent: Thursday, October 23, 2008 7:30 AM > If you are asking about the non-composition of equality in records, > that is certainly not lost. The intent was to allow comparison for > records to be implemented as a bit for bit* comparison. The same > holds true for arrays, at least for equality of arrays of boolean and > enumeration types. I do remember lots of discussion prior to Ada 83 > being finalized about equality for floating point in the presence of > potentially unnormalized values. But that basically went away with > the hardware that permitted it. Both the IEEE and DEC floating point > formats have a single valid representation for every value--at least within types. not true, +0.0 and =0.0 must compare equal! **************************************************************** From: Randy Brukardt Sent: Thursday, October 23, 2008 7:12 PM > You weren't allowed to redefine equality for non-limited types in Ada > 83, so the problem didn't arise. > Why weren't you allowed to redefine equality? I suspect because you > weren't allowed to redefine assignment, and there is an implicit > connection between equality and assignment. Well, obviously that wasn't true in practice (the Goodenough trick was widely known and used to redefine non-limited "="), but I suppose it explains why the issue wasn't seriously considered. But that then begs the question of why you didn't just fix it in Ada 95 (any existing code would have had to have been on the margins of the language) as opposed to leaving it broken so that now it is far more likely to have occurred in practice. I suppose compatibility concerns were brought up then, too, and you were very sensitive to this kind of incompatibility. **************************************************************** From: Robert Dewar Sent: Thursday, October 23, 2008 7:20 PM This reminds me a little of the Fortran discussion. In old Fortran, DO loops always traveled at least one even if the high bound was less than the low bound. This was ugly, and the Fortran committee wondered whether to change it. They did an extensive survey and found one case where it mattered, and it was a bug! Similarly, our survey of existing code found only a couple of cases where it mattered, and one was a latent bug, where the author thought it composed, but luckily it happened to work anyway, and other was a real bug, where the author thought it composed, and the fact that it didn't was a real bug. Based on that I think we should do what the Fortran commmittee decided to do .. fix the bug! **************************************************************** From: Robert Dewar Sent: Thursday, October 23, 2008 7:21 PM Note incidentally that I am normally in a mode of being fanatic about avoiding upward incompatibilities, so I am giving my thoughts on this issue in light of that basic attitude. **************************************************************** From: Randy Brukardt Sent: Thursday, October 23, 2008 7:29 PM I know. I've been wondering if someone else at AdaCore is using your e-mail account. ;-) **************************************************************** From: Robert Dewar Sent: Thursday, October 23, 2008 7:44 PM Well compatibility is a pragmatic issue not a theoretical issue, so the issue is will existing customers be affected? Sometimes we make changes (like 'Size in Ada 95, which are not theoretically compatibility issues, but are in practice). When we make inncompatibloe changes, the issue is not whether there exist probels in theory, but whether these problems exist in practice. As Yogi Berra said, In theory, practice and theory are the same, in practice they are different. **************************************************************** From: Randy Brukardt Sent: Thursday, October 23, 2008 7:25 PM > > If you are asking about the non-composition of equality in records, > > that is certainly not lost. The intent was to allow comparison for > > records to be implemented as a bit for bit* comparison. The same > > holds true for arrays, at least for equality of arrays of boolean and > > enumeration types. I do remember lots of discussion prior to Ada 83 > > being finalized about equality for floating point in the presence of > > potentially unnormalized values. But that basically went away with > > the hardware that permitted it. Both the IEEE and DEC floating point > > formats have a single valid representation for every value--at least within types. > > not true, +0.0 and =0.0 must compare equal! Not to mention that it doesn't work for one's complement Integers (as we found out on the U2200 - +0 and -0 had better compare equal) nor for records with holes (you could initialize the holes to zero, but record object creation is much more likely than record comparison - at least in the code we looked at - so optimizing the less likely operation doesn't make much sense). The idea that you can alway use a bit comparison is bogus. You can use bit comparisons in some cases (it is worth optimizing them to bit string comparisons, and in fact we did so from day one), but you can't do so much of the time. It's much the same as streaming in that sense (there is a lot of value to bit streaming of a record, but you can only do it in limited cases). **************************************************************** From: Robert Dewar Sent: Thursday, October 23, 2008 7:42 PM Right, so compilers already almost certainly have the needed circuitry. I know that in GNAT this would be a VERY easy change to make, probably a line or two. **************************************************************** From: Steve Baird Sent: Thursday, November 13, 2008 7:20 PM At the Portland meeting we decided to treat untagged records in the same way that tagged records have always been treated with respect to the definition of the predefined "=" operator for an enclosing composite type. As I remember it, we did not discuss how this is supposed to work in the case where the user-defined "=" op for the untagged record type is not visible at the point of the declaration of the enclosing composite type. Given this example, package Pkg1 is type T1 is record X, Y : Integer; end record; private function "=" (L, R : T1) return Boolean; end Pkg; with Pkg1; package Pkg2 is type T2 is record A, B : Pkg1.T1; end record; end Pkg2; how should T2's equality operator behave? **************************************************************** From: Tucker Taft Sent: Thursday, November 13, 2008 7:41 PM For tagged types, we say that even if an overriding takes place in the private part, the code executed for a call on a primitive operation is independent of visibility. I think if we are going to try to make record equality work the same whether the type is tagged or not, we should probably adopt this principle for all record equality. In other words, so long as the "=" is overridden somewhere in the visible or private part of the package, it shouldn't matter whether it is visible or not. In short: visibility shouldn't affect composability. We should probably also adopt the "no late overriding" rule for record equality, disallowing overriding after the type has been frozen (and definitely not allowing it in a body). **************************************************************** From: Randy Brukardt Sent: Thursday, November 13, 2008 8:30 PM That seems like adding a compatibility issue where one is not necessarily needed. Wouldn't it make more sense to simply say that "late" overridings don't participate in composition? Existing code with late equality definitions surely doesn't depend on composition, and breaking it solely so composition (that they don't use) works seems like a good way to get the idea dropped. If we were starting from scratch, you surely would be correct. Overriding would work very differently for untagged types (and then we wouldn't need reemergence in generics, either). But as it is, I'd suggest a compiler warning on late overriding and nothing more. **************************************************************** From: Tucker Taft Sent: Thursday, November 13, 2008 8:51 PM I am only proposing "no late overriding" for record equality. I think it is important to make the late overriding illegal, to avoid confusion where the programmer might presume they are getting composable equality when they aren't. I think this is a very minor incompatibility, and it is clearly one that would be caught by the compiler. If we believe that providing composability for equality will fix more bugs than it will introduce, we should make illegal any situation where you *don't* get composability when it seemed like you might. Having a "half-and-half" situation seems dangerous, since there will be no easy answer to what user-defined equality operators are composable. That isn't doing anybody any favors. By the way, when I use the term "late overriding" I mean for a type declared in a package spec, overriding a primitive in the package body or after the type is frozen. That's not allowed for tagged types, but it is allowed for untagged types (and it's a bit of a pain to implement, by the way ;-). **************************************************************** From: Randy Brukardt Sent: Thursday, November 13, 2008 9:33 PM > I am only proposing "no late overriding" for record equality. That's all I thought. ... > By the way, when I use the term "late overriding" I mean for a type > declared in a package spec, overriding a primitive in the package body > or after the type is frozen. > That's not allowed for tagged types, but it is allowed for untagged > types (and it's a bit of a pain to implement, by the way ;-). I could see banning it in a spec (it's easy to fix by moving the declaration up a few lines), but doing it in a body seems like its adding a compatibility problem for no reason. No sane person would expect a routine that the compiler doesn't even know about to participate in composibility. And the routine might depend on stuff in the body, or might really only be intended for local use. (I have debugging equality routines like that.) Making the user put it into the spec (thus making it available to the world) or making them rename it seems like changes that could only cause bugs (it surely isn't going to fix any - they're not depending on composible equality). Remember, the GNAT survey didn't find many instances of anyone using a potentially composed equality; but that doesn't mean that there isn't use of redefined equality, even locally only. P.S. I wouldn't consider anything that happens in the body as "overriding" anyway. "Overriding" is really about inheritance, and there isn't any of that going on in a body. **************************************************************** From: Tucker Taft Sent: Thursday, November 13, 2008 9:51 PM > P.S. I wouldn't consider anything that happens in the body as "overriding" > anyway. "Overriding" is really about inheritance, and there isn't any > of that going on in a body. Ahhh, but there you are wrong! Primitives that are overridden *are* inherited, even if the overriding happens very "late" (e.g. in the body). E.g.: package P is type T is ... -- *not* tagged end P; package body P is function "="(L, R: T) return Boolean is ... -- late overriding type T2 is new T; -- This gets the new "=". ... end P; Cool, eh? ;-) I think this should be illegal for record equality. Did you imply in your earlier message that you actually do this kind of thing? Wouldn't you expect composability if it were legal? **************************************************************** From: Randy Brukardt Sent: Thursday, November 13, 2008 10:42 PM > Cool, eh? ;-) Are you suggesting a new ACATS test?? ;-) (For the record, Janus/Ada passes this test, so I suspect there already is such an ACATS test, probably from the Goodenough era. I've attached the full test below if you're as curious as I was...) > I think this should be illegal for record equality. > Did you imply in your earlier message that you actually do this kind > of thing? Wouldn't you expect composability if it were legal? I surely didn't derive an untagged type in a body; I consider deriving untagged types to be a sin just a bit below using a coextension. ;-) (The only reason I've done it is to "rename" the primitives of a generic easily, and I'd really have preferred to be able to do that without creating a new type. Dan E. is right on that issue.) I did declare an untagged equality in a body for debugging purposes (can't remember if it was named "=", though, although that doesn't matter too much as it surely could have been). I did it because I *only* wanted to debug stuff going on in that body, and I surely did not want it affecting anything else. If I had wanted *that*, I'd have put it in the spec so the world could see it! So I think your suggested new requirement would be actively harmful in this particular case. I don't want to imply that this is some sort of deal-breaker, because it's not: user-defined record equality isn't that common, and besides, all new types ought to be controlled anyway. :-) I just don't see the point of worrying about what it does enough to want to break existing code -- I doubt there is much composing going on in package bodies anyway, especially of types declared locally. ---- with Ada.Text_IO; procedure Tuck3 is package P is type T is record -- *not* tagged C : Character; end record; end P; package body P is function "="(L, R: T) return Boolean is -- late overriding begin return True; end "="; type T2 is new T; -- This gets the new "=". Obj : T2 := (C => '1'); begin Ada.Text_IO.Put_Line ("--- Test Tuck3 - Test late overriding" & " of = for untagged types"); if Obj = (C => 'A') then Ada.Text_IO.Put_Line ("-- OK"); else Ada.Text_IO.Put_Line ("** Wrong = used!"); end if; end P; begin null; end Tuck3; **************************************************************** From: Steve Baird Sent: Friday, November 14, 2008 2:56 PM > For tagged types, we say that even if an overriding takes place in the > private part, the code executed for a call on a primitive operation is > independent of visibility. > I think if we are going to try to make record equality work the same > whether the type is tagged or not, we should probably adopt this > principle for all record equality. At the risk of belaboring the obvious, I want to confirm that this is not just about composability and the equality operators of enclosing composite types. You are saying that a call to Pkg1."=" from outside of Pkg1 should execute the user-defined "=" function. Correct? I agree that this would imply that a post-freezing-point "=" declaration that would have been illegal for a tagged type should also be illegal for an untagged record type. We seem to have implicitly broadened the scope of this AI; I just want to make this explicit. **************************************************************** From: Tucker Taft Sent: Friday, November 14, 2008 3:13 PM I agree, this could be perceived as broadening the AI, but it seems difficult to define what composability means if it is affected by visibility. And it would be pretty weird if the directly called equality didn't agree with the composed equality. We could avoid this problem by disallowing not only late overriding of record "=", but also disallowing overriding in the private part. That would ensure that you would either get a compile-time error, or always get the same semantics as Ada 2005 for a direct call on "=". That might be a nice thing to be able to say. Overriding in the private part would be weird for an untagged type, and might imply some kind of weird intent, so it might be best to disallow it in exchange for getting full composability. **************************************************************** From: Steve Baird Sent: Monday, November 17, 2008 7:20 PM I agree with your analysis. Before I attempt wording, I would like to know if there is general agreement that the attached !problem and !proposal sections describe where we want to go with this AI. [This is version /02 of this AI - Editor] **************************************************************** From: Randy Brukardt Sent: Monday, November 17, 2008 4:55 PM You have the following as statically illegal: > 1) An untagged record type with a user-defined abstract > primitive "=" op. > 2) An untagged record type declared in the visible part of a package > with a user-defined primitive "=" op declared in the private > part or the body of the package. > 3) An untagged record type with a user-defined primitive "=" op > where the predefined "=" op is referenced before it is > overridden. It's not clear to me what you mean by this, at least in the first case. I originally thought that you meant that calling "=" for such an equality would be illegal. That would be what the programmer intended, but I don't see how to do that without nasty generic contract problems. OTOH, if you mean that declaring an abstract "=" is illegal, that seems like a significant incompatibility. For one thing, that would render ASIS '99 illegal (and leave us stuck again with the problem of how to change that for the new ASIS, one that we could not usefully decide any improvement to the last time we looked at it). It also means that anyone who tried to undefine "=" as ASIS did would be broken (and left with no workaround other than redefining "=" to raise an exception -- going from compile-time to run-time detection of errors is not going in the right direction). So this doesn't seem like a good idea, either. My understanding is that Tucker suggested that abstract equality would essentially work as it does now (the predefined "=" would re-emerge in cases where a direct illegality of the call is impossible), since it isn't reasonable to make all such cases statically illegal. (Your quote of him says as much.) You seem to be trying to make all such cases illegal, reasonable or not. The best idea I can think of would be that if a component that has a record type has abstract equality, then the composed equality is also abstract. (This case can't happen currently for a non-abstract tagged type, because abstract operations are only allowed on abstract tagged types. In the abstract case, the result would be the same.) That would make direct calls to "=" illegal in that case. Generics, however, would still re-emerge. **************************************************************** From: Tucker Taft Sent: Monday, November 17, 2008 4:54 PM I would agree with all but the case of an abstract primitive "=". That seems to be a useful way to say that although copying is well defined, there isn't a single "=" that always makes sense. To preserve "the principle", the "composed" equality for a type with such a type as a subcomponent would also be abstract. Any type with an abstract composable "=" could not be passed as an actual to a non-limited formal private or derived type (unless the ancestor type of the formal derived type similarly has an abstract "="). That's pretty draconian, but not as draconian as making the abstract "=" illegal from the get-go. **************************************************************** From: Randy Brukardt Sent: Monday, November 17, 2008 5:02 PM As soon as I pressed "send" on my reply to Steve, I thought of a Baird-like problem with this. (Since this is Steve's AI, someone else will have to take his role of blowing up all of the good ideas!) This solution (proposed by both you and me without knowledge of the other) doesn't work for a concrete tagged type that has an extension component that has an abstract "=" operation. (Such a component would have to be untagged.) In that case, this rule would make the "=" abstract, but that would violate the principle that no concrete tagged type has an abstract operation. And that principle is very important if dispatching is allowed. One could make an exception and have "=" re-emerge in that case, but once you do that, why bother with any of it?? So I now have killed off *all* of the (static) ideas for this one. Anyone else have an idea??? **************************************************************** From: Tucker Taft Sent: Monday, November 17, 2008 5:14 PM I think this is fairly straightforward. The tagged type gets an abstract predefined "=" and if the tagged type is not itself abstract, then the "=" must be overridden. This would be true similarly if its parent type had an abstract "=". **************************************************************** From: Tucker Taft Sent: Monday, November 17, 2008 4:57 PM I guess I would also allow you to "squirrel away" the predefined "=" by renaming it before it was overridden. I'm not sure whether you intended that to be illegal as well, but it is frequently useful. **************************************************************** From: Steve Baird Sent: Monday, November 17, 2008 7:11 PM This one is turning out to be more complicated than I expected. I've attached another attempt at a !discussion and !proposal section, featuring (by popular demand) support for squirreling renames of equality operations that are going to be overridden by abstract functions. [This one is version /03 of the AI - Editor.] **************************************************************** From: Tucker Taft Sent: Monday, November 17, 2008 8:16 PM > [Even if #2 and #3 get the usual boilerplate about rechecking in an > expanded spec, there is still a question about violations within an > expanded body. What are the dynamic semantics of a call to an > overrider-pending "=" operator of a type derived from a formal private > untagged type where the actual turns out to be a record type (tagged > or untagged)? Or do we assume the worst and reject the generic body?] I think you assume the worst in a generic body. That is, if you derive in the body from a nonlimited, untagged formal private type, you should presume it might be an untagged record type, and enforce the restrictions. I can't imagine this will be too constraining! **************************************************************** From: Pascal Leroy Sent: Tuesday, November 18, 2008 2:30 PM > 1) The "composed" equality operator for a type with a component > whose primitive "=" operator is abstract is also abstract. > In the case of a nonabstract tagged type, this operator would > have to be overridden just like any other abstract operation > (ditto in the case of a nonabstract extension of a parent type > which has an abstract "=" operator). Rewording 3.9.3(4/2-6/2) to add this rule to the mix is going to be great fun. **************************************************************** From: Randy Brukardt Sent: Thursday, December 4, 2008 11:19 PM > I think you assume the worst in a generic body. That is, if you > derive in the body from a nonlimited, untagged formal private type, > you should presume it might be an untagged record type, and enforce > the restrictions. I can't imagine this will be too constraining! Humm; generally we want regular private types and formal private types to work the same way. And even though Mr. Private is (mostly) retired, I doubt that we want to break privacy to make such a check. So I think we have to enforce these restrictions on *all* untagged private types (because they might actually be an untagged record type). For instance: package P is type Priv is private; private type Priv is access ...; -- (1) -- type Priv is null record; -- (2) end P; with P; package Q is type New_Priv is new Priv; private function "=" (Left, Right : New_Priv) return Boolean; -- (3) end Q; We can't allow (3) to be legal (it would violate Steve's condition #2), even though the full type is an access type. Because if we did, a change from the full declaration (1) to (2) would make it illegal, and that would be very bad (totally privacy breaking). So I don't think we need to worry about when it would be legal in a generic instance, since the usage has to be illegal all of the time in the template. I cannot get too worried about incompatibilities that only occur for derived untagged types (they don't happen much) with overriding routines (they almost never happen). But it is a wider set of incompatibilities than we originally envisioned. **************************************************************** From: Tucker Taft Sent: Friday, December 5, 2008 12:00 AM I agree this is a simpler and better solution. Namely, all untagged private types, whether formal private or package private, are treated as though they are untagged record types, so no "late" or "private" primitive definition of "=" may be given for any descendant of one. **************************************************************** From: Pascal Leroy Sent: Friday, December 5, 2008 12:00 AM > Humm; generally we want regular private types and formal private types to > work the same way. And even though Mr. Private is (mostly) retired, I doubt > that we want to break privacy to make such a check. Retired but watchful. I think you are perfectly right, you need to reject the private "=" in your example. Not only is this AI getting more and more hairy (hairier and hairier?), but it looks less and less useful with all these weird restrictions that are going to puzzle users. **************************************************************** From: Pascal Leroy Sent: Friday, December 5, 2008 12:11 AM At this point I'd start to worry about the impact of the incompatibilities. **************************************************************** From: Randy Brukardt Sent: Thursday, December 4, 2008 11:26 PM Why do I feel like this AI is like a sinkhole? If you get too close to the edge, another chunk breaks off and falls into the ever-widening pit... ;-) **************************************************************** From: Bob Duff Sent: Friday, December 5, 2008 1:58 PM So maybe we should back off from this particular pit. We've lived with this language bug for many years now... **************************************************************** From: Tucker Taft Sent: Friday, December 5, 2008 2:07 PM Nah. Let's get it right, and then make an informed decision. Writing an AI is like making sausage. The final result may be great, but you don't want to look too closely at the process of getting there. **************************************************************** From: Bob Duff Sent: Friday, December 5, 2008 4:23 PM Well, let's not fall into the trap of "we've done a lot of work on X, so we have to include X in the final thing, or all that work will be wasted". It's easy to do that, subconciously. I think that's what happened with anonymous access types. It would have been better to leave them out, IMHO, since they don't solve the problems they were intended to solve. Which reminds me: I need to respond to the T'Ref thread. I'm afraid I'm rather negative on the idea of "fixing" anon acc types. Also, in a world where only one implementer has implemented Ada 2005, and only just barely, I'm not inclined to spend a lot of energy on Sausage 2015 -- I think it should be a modest update, compared to Adas 95 and 2005, which were both quite huge. It would be good for Ada, and good for the world, to have more than one up-to-date Ada implementation. **************************************************************** From: Randy Brukardt Sent: Friday, December 5, 2008 8:24 PM > At this point I'd start to worry about the impact of the incompatibilities. I do, too, but you have to keep in mind that these incompatibilities can only happen when you derive from an untagged type AND provide a new "=" operator for the derivation. The next time I do that will be the first (and I've been programming in Ada for 28 years!). Once Steve writes up the exact rules, we can try to find out if any incompatibility appears in practice (much as was done in the first place). After all, this entire notion is incompatible (inconsistent, in fact, with no compile-time detection available). But it still seems like a good idea. It also should be noted that none of these rules seem absolutely necessary; we simply could revert to no composition in such cases. But (presuming that we don't find significant real incompatibilities) that seems to just be adding a new source of bugs. **************************************************************** From: Pascal Leroy Sent: Saturday, December 6, 2008 5:23 AM Hmm, I am a bit lost in the discussion, with no wording in the AI and comforting statements like "It is not clear how this principle applies in a some corner cases". I'd like to understand what happens in a case like this: package P is type T is private; private package BS is new Ada.Strings.Bounded_Strings.Generic_Bounded_Length(80); type T is new BS.Bounded_String; end P; Here Bounded_String has a user-defined "=", which is inherited by T. Does that mean that the above example is illegal? Hopefully I am confused and it is not, otherwise it is an horrendous incompatibility. **************************************************************** From: Tucker Taft Sent: Sunday, December 7, 2008 8:27 AM We will definitely need to do some wording if we want to assess the incompatibility. Here is paragraph 15 from 4.5.2, with possible changes marked: For a private type, if its full type is [tagged]{a record type}, predefined equality is defined in terms of the primitive equals operator of the full type; [if the full type is untagged]{otherwise}, predefined equality for the private type is that of its full type. Given the above, I'm not sure we need to worry so much about private types. I think the thing we need to worry about are record types. For those, the predefined "=" may only be overridden visibly. Overriding it privately or after the freezing point should be illegal. **************************************************************** From: Randy Brukardt Sent: Monday, December 8, 2008 1:37 PM But "full type" is the problem here: how is that enforced in generics for private formals and when deriving from a private type whose full type is a record type? You can't break privacy to make such a legality check, so you have to assume the worst. Which is how it ends up applying to private types. Now, I think it is perfectly reasonable to forget the legality rules here and only make the change you note above. (That's a dynamic semantics rule, after all, it can ignore privacy.) The problem comes from trying to enforce points of declaration statically. **************************************************************** From: Tucker Taft Sent: Tuesday, December 9, 2008 12:16 AM > But "full type" is the problem here: how is that enforced in generics > for private formals and when deriving from a private type whose full > type is a record type? Hmmm. The above doesn't really talk about a type *derived* from a private type. In fact, I'm not sure the term "full type" is well defined for a type derived from a private type. One model of a private type is that it is somewhat like a record type with the full type as its component type. This would perhaps argue for making the primitive equality of any private type "composable." The tricky thing there is that you would get a different answer about composability depending on the "view" of the type. That can't be good. We really want composability to be a view-independent feature of the type. On the other hand, privacy argues that if you derive from a private type and there is no place within the immediate scope of the derivative where the parent type is non-private, then it should/could be treated as though it *might* be a record type, and any overriding of equality must satisfy the rules for a record type, and becomes composable. This would apply within a generic body with a formal private type, where deriving from it could presume it *was* a record type, with composability provided to overridings of the equality operator. Derivatives in the spec would follow the rules associated with the actual type, since we want all views of the derived type to follow the same rules. Alternatively, we could say that any derivative of a private type must follow the restrictions that apply to record types, namely no "late" or "private" overrides of equality, but that composability is only provided if the type is a record type "deep down." This alternative seems simpler. To summarize: composability is a dynamic semantics property and sees through privacy, applying to types whose "underlying" type is a record type. The legality restrictions on overriding "=" presume the worst, namely that when you don't have the full view of a private type then presume it *might* be a record type (even though you don't actually get any "benefit" from the restrictions unless the type is *really* a record type). This would ensure that only record types would have different dynamic semantics, and that *if* the equality operator is composable, it is the only equality operator that ever applies to the (record) type. **************************************************************** From: Steve Baird Sent: Tuesday, December 9, 2008 1:48 AM So we get a change in the dynamic semantics of predefined equality operators and a legality rule prohibiting "late" user-defined equality operators in some cases. It seems like we need other changes as well if you still want to treat squirreling renames and abstract equality operators as they were discussed in earlier posts. What is your current thinking about these issues? I'm just trying to understand the full extent of this AI without glossing over any details. **************************************************************** From: Tucker Taft Sent: Tuesday, December 9, 2008 8:20 AM I think we still want squirreling via renames and require overriding or inherit abstractness, as appropriate, if a component has an abstract, composable, equality. I presumed that was independent of these other legality issues, but perhaps not... **************************************************************** From: Steve Baird Sent: Tuesday, December 9, 2008 9:26 AM I was thinking about an interaction between the rule you described for the predefined equality operator of a private type and squirreling renames. In this example, package Pkg is type T is private; function Eq1 (L, R : T) return Boolean renames "="; private type T is record F1, F2 : Integer; end record; function Eq2 (L, R : T) return Boolean renames "="; function "=" (L, R : T) return Boolean; end Pkg; we have decided that a call to Eq2 should not invoke the user-defined "=" operator. What about a call to Eq1? **************************************************************** From: Tucker Taft Sent: Tuesday, December 9, 2008 9:50 AM I would hope we get the same answer if we add "tagged" to the record declaration. One key goal is to make tagged and untagged records work the same way, at least with respect to equality, so there are fewer perverse incentives to make a record type tagged just so that it "works right." I really don't want us to end up in a situation where tagged record equality works one way, untagged record equality works a second way, and non-record equality works a third way. I realize tagged types have private extensions, which don't really have an analogy in the non-tagged world, but hopefully untagged derivation would work very much like null tagged record extensions with respect to equality, whether the parent type is a visible record type or a private type whose full type is a record type. **************************************************************** From: Steve Baird Sent: Friday, March 20, 2009 7:40 PM In Thursday's conference call, it was decided that I should attempt wording for AI05-0123. The result of that decision is attached. [This is version /04 of the AI - ED] **************************************************************** From: Randy Brukardt Sent: Friday, March 20, 2009 9:15 PM >formatting error (wrong font, bad indentation) for >#2 in the "These cases are dealt with as follows" list, starting with >"type may not be declared". Well, the formatting of the on-line viewer isn't technically part of an AI, but I suppose it doesn't hurt to fix it. The formatter has to guess on the fly if a line represents code, generally by looking at the first couple of words/symbols. If it guesses wrong, well, you get some ugly output. The guessing assumes that code is indented, and then checks whether lines start with something that looks like code. That means that indented lines in AIs ought not start with words that are Ada reserved words that start a declaration or statement: "procedure", "function", "type", etc. In this case, the second line starts with "type", and thus the formatter screws up. All that needs to be done is to move "type" to the previous line, and all is well. >!discussion (not intended to replace the existing !discussion section) Well, since the existing one consists solely of "**TBD**", I think we can safely replace it. ;-) [Now to actually read your AI...] >What about this one? > > package P is > type T is limited private; > package Inner is > type Rec is record X : T; end record; > end Inner; > private > function "=" (L, R : T) return Boolean is abstract; > type T is null record; > end P; > > package body P is > package body Inner is > X, Y : Rec; > Flag : Boolean := X = Y; > end Inner; > end P; > >We want to reject this example, but it's not clear what rule needs to >be invented to handle this case. Shouldn't we simply make the declaration of "=" illegal by analog with 3.9.3(10)? That's what's done for tagged types, and I can't quite imagine why this ought to be a hidden property. That might make something or other impossible, but what is the point of trying to figure this one out?? If the declaration is in the visible part, the abstract "=" is a visible property, so Rec "clearly" has an abstract "=". --- As to the question of arrays, I'm not sure. It doesn't look like it would simplify the wording any to extend this to all composite types. (I doubt it would make it longer, but I don't see any simplifications, either.) I think your argument boils down to "anyone that redefines "=" without redefining "<=" compatibly is nuts, so we don't care what happens to them". Maybe that's OK. **************************************************************** From: Steve Baird Sent: Monday, April 6, 2009 11:18 AM > Steve: Make a new draft of AI-123. I did this and the writeup reflects the discussions of the group. However, it seems to me that the measures we are taking to statically prevent the invocation of an abstract equality operator are not worth the cost. How about a change in dynamic semantics so that this case may be allowed to occur (at least in some cases) and has well-defined dynamic semantics? If we want to, we could still statically reject the cases where we know precisely what is going on, as opposed to imposing assume-the-worst rules. Alternatives include - for purposes of deciding whether an untagged unlimited record type has a user-defined equality op (in this AI), ignore abstract equality ops (i.e. revert to existing Ada05 semantics in this case). - raise Program_Error at the point where an abstract equality operation would otherwise be invoked. I would favor the latter because presumably the user had a good reason for declaring the equality operator to be abstract. **************************************************************** From: Randy Brukardt Sent: Monday, April 6, 2009 12:58 PM I would be in favor of the second, since it seems like a better alternative than adopting a mess of incompatible and weird assume-the-worst static rules. Indeed, I've tried to make the suggestion on considering dynamic rules several times in the past without much interest. Perhaps you need to write up an alternative set of rules so we can see whether it helps any to do that. (I think squirreling renames still would be a problem, for instance.) **************************************************************** From: Bob Duff Sent: Sunday, April 12, 2009 3:41 PM > Is the cure worse than the disease? > Should this whole AI should be abandoned? I (reluctantly) think so. I say "reluctantly", since I was originally strongly in favor of this, but now I see it's getting out of hand. It's just not worth it. At this point, I think the cure is indeed worse than the disease. **************************************************************** From: Randy Brukardt Sent: Monday, June 29, 2009 11:05 PM I'm updating AI-123 for our meeting notes, and I think the wording Steve proposed is ambiguous. Each time I read, the more ambiguous it appears. In several places, he replaced "tagged" with "record" thus: For a type extension, predefined equality is defined in terms of the primitive [(possibly user-defined)] equals operator of the parent type and of any {record}[tagged] components of the extension part, and predefined equality for any other components not inherited from the parent type. The problem here is that "record components" has a well-used meaning which is definitely not what Steve means here. Indeed, all components of an extension part are "record components". He really means "components of a record type", and we ought to say that here: For a type extension, predefined equality is defined in terms of the primitive [(possibly user-defined)] equals operator of the parent type and of any [tagged] components {of a record type} of the extension part, and predefined equality for any other components not inherited from the parent type. This is a bit annoying because of the two "ofs", but at least it isn't ambiguous. The other wording is even more ambiguous: * Otherwise, the result is defined in terms of the primitive equals operator for any matching record components, and the predefined equals for any other matching components. Ugh: even knowing what is meant, I still think this means that you use the primitive equals for all components of a record, and the predefined equals for the components any other kind of composite object. Adam will be all over us! Unfortunately, the simple fix doesn't seem to work: * Otherwise, the result is defined in terms of the primitive equals operator for any matching components of a record type, and the predefined equals for any other matching components. It's still not completely clear. (I note that the original wording was also ambiguous this way. Where's Adam?? ;-) * Otherwise, the result is defined in terms of the primitive equals operator for any matching components that have a record type, and the predefined equals for any other matching components. Seems to work, but it's a bit awkward. Better wording is requested. **************************************************************** From: Randy Brukardt Sent: Monday, June 29, 2009 11:16 PM > * Otherwise, the result is defined in terms of the primitive equals operator > for any matching components that have a record type, and the predefined equals for any > other matching components. > > Seems to work, but it's a bit awkward. I just found an AARM note (that I need to change) that uses "with": * Otherwise, the result is defined in terms of the primitive equals operator for any matching components with a record type, and the predefined equals for any other matching components. That seems better to me. **************************************************************** From: Tucker Taft Sent: Monday, June 29, 2009 11:16 PM ... > For a type extension, predefined equality is defined in terms of the > primitive [(possibly user-defined)] equals operator of the parent type > and of any [tagged] components {of a record type} of the extension part, > and predefined equality for any other components not inherited from > the parent type. Perhaps change some of the "of"s to "for" or "in": For a type extension, predefined equality is defined in terms of the primitive [(possibly user-defined)] equals operator for the parent type and for any [tagged] components {of a record type} in the extension part, and predefined equality for any other components not inherited from the parent type. ... > * Otherwise, the result is defined in terms of the primitive equals operator > for any matching components that have a record type, and the > predefined equals for any > other matching components. > > Seems to work, but it's a bit awkward. Perhaps: * Otherwise, the result is defined in terms of the primitive equals operator for any matching components that are records, and the predefined equals for any other matching components. **************************************************************** From: Randy Brukardt Sent: Monday, June 29, 2009 11:47 PM This AI has: Append after 8.5.4(7.1/1) A corresponding rule applies to a call on a renaming of a predefined equality operator for an untagged record type that is overridden if the overriding occurs after the renaming. This doesn't make any sense to me; 8.5.4(7.1/1) is about renames-as-body, which has nothing to do with squirrelly renames. (Umm, squirreling renames). The very next paragraph is about renames of dispatching calls, which is the analogy that we are trying to preserve: For a call on a renaming of a dispatching subprogram that is overridden, if the overriding occurred before the renaming, then the body executed is that of the overriding declaration, even if the overriding declaration is not visible at the place of the renaming; otherwise, the inherited or predefined subprogram is called. Note that this is about *statically* bound calls to dispatching routines; because if they are dispatching calls, the body called is determined dynamically and has nothing to do with this text. So it is perfectly appropriate here; all we need to add to this is A corresponding rule applies to a call on a renaming of a predefined equality operator for an untagged record type. And we're done. **************************************************************** From: Randy Brukardt Sent: Monday, June 30, 2009 12:32 AM I am wondering how we are preventing reemergence for derived untagged record types. We prevent it for tagged types by defining that "predefined equality" is depends on the primitive equality for the parent type and that of extension components. We also have a rule for private types (which now applies to all record types). But I think we need a similar rule for untagged derived types. If we don't have one, we drop out to 4.5.2(16), and that uses the component-by-component equality to define "predefined equality". Note that the inherited equality is *not* the predefined equality (it is primitive, but not predefined). So I think we need some wording like: For a derived type whose parent is an untagged record type, predefined equality is defined in terms of the primitive (possibly user-defined) equals operator of the parent type. This should follow 4.5.2(15). **************************************************************** From: Tucker Taft Sent: Monday, June 30, 2009 9:26 AM Good point. I agree with your logic and your solution. **************************************************************** From: Steve Baird Sent: Monday, June 30, 2009 9:35 AM ... > For a type extension, predefined equality is defined in terms of the > primitive [(possibly user-defined)] equals operator of the parent type > and of any [tagged] components {of a record type} of the extension part, > and predefined equality for any other components not inherited from > the parent type. > > This is a bit annoying because of the two "ofs", but at least it isn't > ambiguous. I agree that this is an improvement. > The other wording is even more ambiguous: > > * Otherwise, the result is defined in terms of the primitive equals operator > for any matching record components, and the predefined equals for any > other matching components. > > > * Otherwise, the result is defined in terms of the primitive equals operator > for any matching components that have a record type, and the > predefined equals for any > other matching components. > > Seems to work, but it's a bit awkward. > > Better wording is requested. This seems adequate. As you noted. you really need "components that have a record type", as opposed to "components of a record type". Good catches! **************************************************************** From: Ed Schonberg Sent: Monday, June 30, 2009 10:39 AM > I just found an AARM note (that I need to change) that uses "with": > * Otherwise, the result is defined in terms of the primitive equals operator > for any matching components with a record type, and the predefined equals > for any other matching components. > > That seems better to me. I have a slight preference for any matching components that are of a record type or any matching components that have a record type **************************************************************** From: Tucker Taft Sent: Tuesday, October 6, 2009 4:28 PM In looking at AI05-0123, composability of equality, it seems a bit dodgy to relegate the explanation of what we mean by "primitive equality operator" to an AARM note: Currently we have: Add after 4.5.2(9.7/2): The explicit declaration of a primitive equality operator of an untagged record type shall occur before the type is frozen. ... AARM Note: The phrase "equality operator" as used here refers only to a function whose profile is type conformant with that of the predefined equality operator for the untagged record type. Couldn't we reword this to: The explicit declaration of a primitive equality operator of an untagged record type, if its profile is type conformant with that of the corresponding predefined equality operator, shall occur before the type is frozen. ... Although it is somewhat ambiguous whether the term "equality operator" means any function whose name is "=" or "/=", or only one that takes two operands of the same type and returns Boolean, it stills feels funny to relegate to an AARM NOTE the fact that this rule only applies to the latter. Note that in 3.4(17/2) we go out of our way to qualify the rule there with "... would have a profile that is type conformant with the profile of the corresponding predefined equality operator...". It seems we ought to be consistent. Sorry I didn't notice this earlier... ****************************************************************