Version 1.1 of acs/ac-00032.txt

Unformatted version of acs/ac-00032.txt version 1.1
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
!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: 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).

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


Questions? Ask the ACAA Technical Agent