Version 1.4 of acs/ac-00032.txt

Unformatted version of acs/ac-00032.txt version 1.4
Other versions for file acs/ac-00032.txt

!standard 8.6(29)          02-04-26 AC95-00032/01
!class amendment 02-04-26
!status received no action 02-04-26
!subject Preferred interpretations for modular type operations
!summary
!appendix

!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" <pleroy@rational.com> 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 :-)

****************************************************************



Questions? Ask the ACAA Technical Agent