!standard 8.6(29) 02-04-26 AC95-00032/01 !class amendment 02-04-26 !status received no action 02-04-26 !subject !appendix Preferred interpretations for modular type operations !topic Modular types, preferred interpretations !reference RM95-8.6(29),3.5.4(17),4.9(34) !from Adam Beneschan 04-24-02 !discussion Is the following legal? type Modular_Type is mod 5; X : Modular_Type := Modular_Type'Modulus - 1; ACATS test b490001 indicates that this should be illegal, because the static expression should fail Range_Check. However, I don't see why this check should fail. By 3.3.1(4), the expected type of the initialization expression is Modular_Type. By 8.6(24), the initialization expression may legally resolve to a universal type which covers Modular_Type, that is, universal_integer (3.4(6)). This means that the "-" operator that takes two universal integers and returns a universal integer is a possible interpretation of the initialization expression (since 'Modulus returns a value of type universal_integer by 3.5.4(17)). Also, interpreting "-" as the predefined operator on Modular_Type is a possible interpretation; and the expression would fail Range_Check if this interpretation were used, since Modular_Type'Modulus cannot be converted to a Modular_Type. However, by 8.6(29), since there are two interpretations with one using the primitive operator on a root_integer type, and the other using a non-universal type (Modular_Type), the interpretation that uses the root_integer type is preferred. I could be confused about the relation between universal_integer and root_integer, but the note in 3.4.1(12ff) seems to indicate that my understanding is correct. Thus, since the rules dictate that "-" is interpreted as the operator on universal_integer, and since the universal-integer result of "-" is within the base range of Modular_Type, it appears to me that Range_Check will not fail, and therefore, the statement should not violate 4.9(34). So what am I missing? **************************************************************** From: Gary Dismukes Sent: Wednesday, April 24, 2002 6:30 PM Adam Beneschan wrote: > > Is the following legal? No. > type Modular_Type is mod 5; > > X : Modular_Type := Modular_Type'Modulus - 1; > > ACATS test b490001 indicates that this should be illegal, because the > static expression should fail Range_Check. However, I don't see why > this check should fail. > > By 3.3.1(4), the expected type of the initialization expression is > Modular_Type. By 8.6(24), the initialization expression may legally > resolve to a universal type which covers Modular_Type, that is, > universal_integer (3.4(6)). That rule is designed to allow the use of universal literals in specific type contexts, but it doesn't apply to the top level of this expression, which doesn't have a universal type. > This means that the "-" operator that > takes two universal integers and returns a universal integer is a > possible interpretation of the initialization expression (since > 'Modulus returns a value of type universal_integer by 3.5.4(17)). But there isn't any such "-" operator. Type universal_integer doesn't have such an operation (universal types don't have any primitive operations at all, see 3.4.1(7)). There is however a "-" operator for type root_integer that results in a possible interpretation. > Also, interpreting "-" as the predefined operator on Modular_Type is a > possible interpretation; and the expression would fail Range_Check if > this interpretation were used, since Modular_Type'Modulus cannot be > converted to a Modular_Type. That's correct, and that leads to the illegality... > However, by 8.6(29), since there are two interpretations with one > using the primitive operator on a root_integer type, and the other > using a non-universal type (Modular_Type), the interpretation that > uses the root_integer type is preferred. I could be confused about > the relation between universal_integer and root_integer, but the note > in 3.4.1(12ff) seems to indicate that my understanding is correct. The preference rule only applies if there are multiple acceptable interpretations, however the interpretation with a result of type root_integer isn't acceptable in this case, because it doesn't satisfy the expected type context. There's only one acceptable interpretation in this case, that for the "-" of the modular type. The preference rule is basically for allowing cases where there isn't sufficient type context to disambiguate two interpretations where one is root_integer (such as in relational expressions with literal operands). > Thus, since the rules dictate that "-" is interpreted as the operator > on universal_integer, and since the universal-integer result of "-" is > within the base range of Modular_Type, it appears to me that > Range_Check will not fail, and therefore, the statement should not > violate 4.9(34). > > So what am I missing? See the explanation above :-) **************************************************************** From: Jeffery Carter Sent: Thursday, April 25, 2002 10:40 AM I understand this rule. However, the following is legal: Init_Value : constant := Modular_Type'Modulus - 1; X : Modular_Type := Init_Value; This is very confusing to newcomers; for most developers, this rule is understood as "you can't do that for no good reason". It's a rule that's followed because it must be, not because it's understood and makes sense. This seems like a weakness in the standard. I would recommend allowing this to be evaluated using Root_Integer in Ada 0Y if possible. **************************************************************** From: Robert Dewar Sent: Thursday, April 25, 2002 10:26 PM That's an ill-thought out proposal, and I cannot see anyway of implementing this without causing horrible incompatibilities. Well let's nbe more positive. Jeffrey, we can't really even understand what you are suggesting unless you give something pretty close the RM language for the change you have in mind. **************************************************************** From: Pascal Leroy Sent: Thursday, April 25, 2002 2:34 PM > This seems like a weakness in the standard. I would recommend allowing this > to be evaluated using Root_Integer in Ada 0Y if possible. Contrary to what you seem to imply the code fragments: X : Modular_Type := Modular_Type'Modulus - 1; and: Init_Value : constant := Modular_Type'Modulus - 1; X : Modular_Type := Init_Value; are vastly different as the first one could call a user-defined "-" operator, but not the second. You should not be fooled by the similarity of the program texts, there is much more than a simple substitution here. So I don't see how the proposed change could be done in a compatible way. You are suggesting that we call the "-" for root_integer instead of the one for Modular_Type, which is the worst kind of incompatibility, as it may silently change the meaning of a legal program. **************************************************************** From: Jeffery Carter Sent: Thursday, April 25, 2002 5:41 PM Thanks for the concise description of problems with the suggestion. As someone not involved in language definition or compiler implementation, I know that what seems simple may not be so; that's why I said "if possible". In Ada 83, a compiler generally could not use context information to determine the operations in an expression. In this case, the expression containing only universal_integer operands, the universal operation would be selected (IIRC). In Ada 95, compilers can use context, resulting in choosing the operation for the type, even though both operands are universal and the compiler could determine that one of them is out of range for the type, and even though a universal value is legal as an initialization expression. So perhaps I'm saying the use of context to determine the operation has gone too far. If the operands are all universal, the root operation should be chosen. If the developer wants the operation for the type, one of the values may be qualified as being of the type. A similar issue arises when the operation for the type is deliberately not visible (in a non-used package). Developers expect the visible root operation to be selected, since all operands are universal, and are surprised that the expression doesn't compile. I hope I'm not wasting the list's time with this. These are situations that cause developers to say "What the--?" with unfortunate frequency. The 95 revision was supposed to reduce these, not add to them. Since the work around exists, I don't know if this is worth pursuing further. **************************************************************** From: Pascal Leroy Sent: Friday, April 26, 2002 1:49 AM > In Ada 83, a compiler generally could not use context information to > determine the operations in an expression. In this case, the expression > containing only universal_integer operands, the universal operation would be > selected (IIRC). > > In Ada 95, compilers can use context, resulting in choosing the operation > for the type, even though both operands are universal and the compiler could > determine that one of them is out of range for the type, and even though a > universal value is legal as an initialization expression. That's not true (except perhaps for extremely obscure cornercases that have nothing to do with the problem at hand). The main differences between Ada 83 and Ada 95 here is that Ada 83 didn't have modular types. Consider the type declaration: type T is range 1..4; and the two code fragments: X : T := 5 - 1; and: Init_Value : constant := 5 - 1; X : T := Init_Value; In the first fragment, the "-" operator is the one for T, which may be user-defined. In the second fragment, it is the "-" operator for universal_integer, which cannot. Note that if you try to declare a user-defined "-" operator for T, passing 5 for the first parameter will raise Constraint_Error because, well, 5 is larger than 4. In Ada 95 with a modular type, any (static) expression outside the range of the type is rejected, so the problem is detected earlier, which is good. **************************************************************** From: Bob Duff Sent: Friday, April 26, 2002 8:44 AM Carter, Jeffrey writes: > > This seems like a weakness in the standard. I would recommend allowing this > > to be evaluated using Root_Integer in Ada 0Y if possible. I agree that it's a flaw in the language. Whether it's worth fixing, I don't know. Pascal Leroy writes: > Contrary to what you seem to imply the code fragments: > > X : Modular_Type := Modular_Type'Modulus - 1; > > and: > > Init_Value : constant := Modular_Type'Modulus - 1; > X : Modular_Type := Init_Value; > > are vastly different as the first one could call a user-defined "-" > operator, but not the second. You should not be fooled by the similarity of > the program texts, there is much more than a simple substitution here. This reminds me of the usual discussions between programmer and language lawyer. Programmer says, "How come I can't add two and two in the obvious way?" Language Lawyer, "Well, you see, the rules have to forbid the obvious, because you might have used a generic discriminated task with nested variant parts designated by access discriminants." Programmer: "But I just want to add two and two." :-) :-) > So I don't see how the proposed change could be done in a compatible way. > You are suggesting that we call the "-" for root_integer instead of the one > for Modular_Type, which is the worst kind of incompatibility, as it may > silently change the meaning of a legal program. The problem is not in the overload resolution rules. The problem is the special rule about static expressions. Would it not be a simple matter to say that an out-of-bounds static expression of a modular type is reduced modulo the modulus? Is that not what programmers expect? I mean, that's what happens at run time, so why don't we do the same at compile time for static expressions? T'Modulus = 0 (mod T'Modulus), so why can't I write T'Modulus and get zero? I don't see any compatibility problems. In other words, the fix would still call the user-defined or predefined modular "-" in the first example above, but the static expression Modular_Type'Modulus would be implicitly converted to Modular_Type in a more helpful way. (Modular types are one of my least favorite features of Ada!) > In Ada 83, a compiler generally could not use context information to > determine the operations in an expression. In this case, the expression > containing only universal_integer operands, the universal operation would be > selected (IIRC). No, that's not right. The overloading rules have not changed in this regard. Context has always been used to resolve subexpressions. (This is a significant advantage of Ada over, for example, C++, in my opinion.) > I hope I'm not wasting the list's time with this. These are situations that > cause developers to say "What the--?" with unfortunate frequency. The 95 > revision was supposed to reduce these, not add to them. I think you have a legitimate complaint about the language. I don't think it's wasting time. > Since the work around exists, I don't know if this is worth pursuing > further. Perhaps not. Shrug. **************************************************************** From: Christoph Grein Sent: Friday, April 26, 2002 12:49 AM > type Modular_Type is mod 5; > > X : Modular_Type := Modular_Type'Modulus - 1; Why all this discussion? This might be confusing, but we have X : Modular_Type := Modular_Type'Last; No need to change the standard. **************************************************************** From: Robert Dewar Sent: Friday, April 26, 2002 5:45 PM Well yes, of course we know that, but the original code was just an example of a more general problem. Actually this business of whether a user defined or universal operator is used can definitely confuse beginners, but it is something that experienced Ada programmers should have straight :-) **************************************************************** From: Bob Duff Sent: Friday, April 26, 2002 8:48 AM > Note that if you try to declare a user-defined "-" operator for T, passing 5 > for the first parameter will raise Constraint_Error because, well, 5 is > larger than 4. True, although you could define an operator on T'Base, which is closer to the semantics of the predefined operators. >... In Ada 95 with a modular type, any (static) expression > outside the range of the type is rejected, so the problem is detected > earlier, which is good. I'm not sure there's a real "problem", so I'm not sure detecting it earlier is "good". ;-) Maybe the root of the problem is with type conversion to modular. Why do all the other operations do an implicit "mod", and this one doesn't? We argued about that during the language design, and I think perhaps we got the wrong answer in the end. (I don't much like the implicit "mod"'s at all. But if you're going to have implicit mod on "+" and so forth, it seems like you should have consistency.) **************************************************************** From: Randy Brukardt Sent: Friday, April 26, 2002 2:20 PM Bob said: > Why do all the other operations do an implicit "mod", and this one doesn't? Good question. But you get the wrong answer. The real question is why you would want the implicit "mod"; much of the time you'd rather have overflow semantics. Probably the truth is that you DO need both, and they both ought to be fully consistent (implicit "mod" on everything). **************************************************************** From: Adam Beneschan Sent: Friday, April 26, 2002 10:01 PM Gary Dismukes wrote: > Adam Beneschan wrote: > > > > Is the following legal? > > No. > > > type Modular_Type is mod 5; > > > > X : Modular_Type := Modular_Type'Modulus - 1; > > > > ACATS test b490001 indicates that this should be illegal . . . > > But there isn't any such "-" operator. Type universal_integer doesn't > have such an operation (universal types don't have any primitive operations > at all, see 3.4.1(7)). There is however a "-" operator for type root_integer > that results in a possible interpretation. Thank you for the response. I was having trouble understanding the differences between universal_integer and root_integer and how everything fit together, but I believe I'm clear on that now. However, there's still something about this that confuses me. If I understand correctly, the specific rule that makes this code illegal is 4.9(34)---is that correct? Since the only allowable interpretation of "-" is the predefined operator on the modular type, it violates 4.9(34) because the left operand to "-" is out of range for the modular type, and therefore fails Range_Check. But is this right? 11.5(16) defines "Overflow_Check" as checking that a scalar value is within the base range of its type. 11.5(17) defines "Range_Check" as checking that a scalar value satisfies a range constraint, plus some other cases that are irrelevant to this issue. 4.5.3(1-2) indicates that the predefined "-" operator is defined for a type, not a subtype. My understanding was that only subtypes have constraints, and types do not; 3.2 and other sections (such as 3.5.4(10)) bolster this impression. Thus, when Modular_Type'Modulus cannot be used as an argument to "-", it's because the the value is not within the base range of the type, *not* because the value does not satisfy a range constraint. So it would seem to me that the evaluation of (Modular_Type'Modulus - 1) fails Overflow_Check, not Range_Check, and therefore the compiler should *not* reject the expression by 4.9(34). Adding to my confusion is ACATS test c45252a, which contains this code: TYPE LIKE_DURATION_M23 IS DELTA 0.020 RANGE -86_400.0 .. 86_400.0; IF 2.9E9 <= LIKE_DURATION_M23'LAST THEN . . . On a 32-bit machine, Like_Duration_M23 will likely be implemented as an integer with 26 bits to the left of the decimal point and 6 bits to the right (Like_Duration_M23'Small = 0.015625). The base range, therefore, would be approximately -3.3E7 .. 3.3E7, so 2.9E9 falls outside this base range. I'm guessing, though, that when 2.9E9 is passed as the first parameter to this function: function "<=" (Left, Right : T) return Boolean; where T is the base type of Like_Duration_M23, this is supposed to fail Overflow_Check and not Range_Check, because the compiler is *not* supposed to flag this as an error. So in order for c45252a and b490001 both to be correct, it would have to be the case that the out-of-base-range value fails Overflow_Check in the fixed-point case and Range_Check in the modular-type case. What language rules make this the case? Anyway, I've convinced myself that this *is* indeed the case, but perhaps for the wrong reason. Parameter associations for IN parameters involve type conversions, and 4.6(28) says If there is no value of the target type that corresponds to the operand value, Constraint_Error is raised; this can only happen on conversion to a modular type, and only when the operand value is outside the base range of the modular type. So it makes sense that modular types and other types would be treated differently. Still, since this check involves the base range, it would seem that a violation would be an Overflow_Check, by 11.5(16). But the reason I think it's really a Range_Check is . . . because the Index says so. That is, the Index entry for Range_Check points to 4.6(28). This is disturbing, though, because the Index isn't part of the Standard. So unless there's something I'm missing, or unless there's an error in one of the ACATS tests, I think the language rules need to be a lot clearer regarding what a Range_Check and what an Overflow_Check is, in order to know just what is allowed by 4.9(34). I appreciate any comments anyone has regarding this. **************************************************************** From: Robert Dewar Sent: Friday, April 26, 2002 6:21 PM > I agree that it's a flaw in the language. Whether it's worth fixing, I > don't know. I disagree that this is a flaw, and I am afraid any attempt to "fix" it would make things worse. **************************************************************** From: Robert Dewar Sent: Friday, April 26, 2002 10:41 PM I really don't think there is any interesting language revision issue here. Shouldn't questions be directed elsewhere. **************************************************************** From: Pascal Leroy Sent: Monday, April 29, 2002 3:44 AM > I'm not sure there's a real "problem", so I'm not sure detecting it > earlier is "good". ;-) Maybe the root of the problem is with type > conversion to modular. Why do all the other operations do an implicit > "mod", and this one doesn't? We argued about that during the language > design, and I think perhaps we got the wrong answer in the end. > > (I don't much like the implicit "mod"'s at all. But if you're going to > have implicit mod on "+" and so forth, it seems like you should have > consistency.) That doesn't strike me as inconsistent. It seems to me that it nicely mimics the mathematical properties of the set of integers modulo m: the operations "wrap around", but numbers outside the range 0..m-1 do not belong to the set. **************************************************************** From: Robert Dewar Sent: Monday, April 29, 2002 2:00 PM The discussion in the Sherwood Forest clearly understood that modular types were going to be subject to some issues when it came to modular behavior vs overflow behavior. I think that discussion was well informed, and reached the right conclusion, i don't see any improved suggestions on the table, even discounting compiatibility issues. **************************************************************** From: Tucker Taft Sent: Monday, April 29, 2002 4:14 PM We explicitly discussed the issue of whether explicit or implicit conversion should be wrap-around, and I remember Randy providing some persuasive scenarios why it was better to not do the wraparound on conversion. If we *did* provide wraparound on conversion, it would probably want to be reversible as well, but that is pretty hard to define. For example: type Mod_Int is mod 2**Integer'Size; procedure Test(Y : in out Mod_Int) is begin null; end; X : Integer := Integer'First; ... Test(Mod_Int(X)); -- What is the value of X at this point if conversion is wraparound? -- is it back to Integer'First? Another reason not to provide wraparound on implicit conversion from Universal integer was there would be less help in detecting typographical errors, e.g. when writing 16#7FFFFFFFF# (9 digits instead of 8) when you should have written 16#7FFFFFFF#. (The reverse conversion problem seems the more troublesome.) **************************************************************** From: Robert Dewar Sent: Monday, April 29, 2002 4:33 PM Indeed, that was the Sherwood Forest discussion I mentioned. Somewhere someone should have the minutes of this meeting, though frankly I see no reason to rediscuss this issue, nothing new has come up. **************************************************************** From: Randy Brukardt Sent: Tuesday, April 30, 2002 3:26 PM Robert said: > The discussion in the Sherwood Forest clearly understood ... Are you talking about a 9x DR meeting? I don't recall one that located in anything like "the Sherwood Forest". **************************************************************** From: Randy Brukardt Sent: Tuesday, April 30, 2002 3:37 PM Tuck said: > We explicitly discussed the issue of whether explicit or implicit > conversion should be wrap-around, and I remember Randy providing > some persuasive scenarios why it was better to not do the > wraparound on conversion. I don't doubt it. If you are going to have modular types, they have to be made sensible. My complaint has always been the impossibility of having an unsigned type with the largest integer size and the "safe" overflow semantics of Ada. It seems odd that in this one case, you are forced to choose "unsafe" by the language. (Yes, there are cases where modular semantics are useful; it should be obvious that we need both...) But I doubt anything useful will be done about this. I've been making this complaint for a long time, and every time is comes up, we vote it no-action. **************************************************************** From: Randy Brukardt Sent: Tuesday, April 30, 2002 3:48 PM > Indeed, that was the Sherwood Forest discussion I mentioned. Somewhere > someone should have the minutes of this meeting, though frankly I see no > reason to rediscuss this issue, nothing new has come up. Oddly, while the archives at http://archive.adaic.com/95com/mrtcomments/ contain all of the MRT e-mail, there doesn't appear to be any archive of meeting minutes. Perhaps its in the mail, but I'd need some idea of when this meeting took place (I recall a significant discussion of modular types after our UI team did a fairly complete implementation, which proved to my surprise that non-binary modulii were not hard nor inefficient to implement. But that may not be what you have mind.) **************************************************************** From: Alexandre Kopilovitch Sent: Monday, April 29, 2002 4:31 PM "Pascal Leroy" wrote: >even if you started a language >design from scratch, you could come up with a set of rules that would be free >from "unintuitive" consequences. Of course, you could do what the other >languages do, i.e. keep the rules fuzzy enough that it's impossible to reason >on their consequences. There exists another way to deal with such "unintuitive" consequences. Perhaps, quite expensive way, but nevertheless valid. Consider a "manifold" notion in the differential geometry/topology. A common manifold (sphere, for a example) can't be coordinatized as a whole, that is, one can't impose single coordinate system that exactly covers the manifold. But several coordinate system easily covers the manifold, and when we impose the requirement for these coordinate systems to be compatible (in some strict sense) to each other on intersections, we obtain a quite useful coordinates, with which we may perform _local_ computations. Applying this metaphor to a programming language definition, several overlapped areas (or "mental modes") may be defined. What is "intuitive" in one area isn't necessarily intuitive in another, but all areas follow the same type of logic, and inside the interesections, locally, the considerations always look very similar, almost isomorphic. This way, all rules can be made "natural" (that is, without "unintuitive" consequences - locally, of course) - in at least one area. Not all those areas should be presented in foreground for the real programmers, so there is no need to pursue the same detalization of the presentation for all areas. Those "shadow", but explicitly defined and outlined areas may be useful for 1) references, 2) discussions (like that provoked this message), 3) extension of a language subsets used for formally provable programs. **************************************************************** From: Robert Dewar Sent: Monday, April 29, 2002 4:32 PM I must comment that I find Alexandre's contribution about general language design way off topic, and of little value. Please let's not have general philosophical discussions. Take them to some discussion group, where vague stuff is not only tolerated but welcome :-) ****************************************************************