comp.lang.ada
 help / color / mirror / Atom feed
From: dewar@cs.nyu.edu (Robert Dewar)
Subject: Re: Zoo question
Date: 1996/08/15
Date: 1996-08-15T00:00:00+00:00	[thread overview]
Message-ID: <dewar.840110364@schonberg> (raw)
In-Reply-To: 3211C462.19D9@lmtas.lmco.com


LET'S CLEAN UP THE ANIMAL CAGES! :-)

Ken's note here has sowed a lot of possibly confusing information, and it
always worries me that people may get the impression that Ada is all screwed
up, so lets unscrew things. Here is Ken's program, slightly modified to
add output and actually call Fill:

   with Text_IO; use Text_IO;
   procedure Zoo_Main is
   
      package Zoo is
      
        type Animal_ID is range 1 .. 5;
        -- yes, an enumeration type might be
        -- better, but it spoils the example!
      
        subtype Cage_ID is Positive range 1 .. 12;
      
        type Cage_Row is array (Cage_ID) of Animal_ID;
      
        -- Fill fills a row of cages with various animals, in
        -- sequential order with alternating animals
        procedure Fill ( Cages : in out Cage_Row );
      
      end Zoo;
      
      package body Zoo is
      
        procedure Fill ( Cages : in out Cage_Row ) is
          Next_Animal : Animal_ID := Animal_ID'First;
        begin -- Fill
          for Cage in Cages'Range loop
      
            Cages(Cage) := Next_Animal;
      
            Put_Line
              ("Cage " & Cage'Img & " contains animal " & Next_Animal'Img);
      
            Get_Next_Animal: begin -- look carefully at this code!
              Next_Animal := Animal_ID'Succ(Next_Animal);
            exception
              when others => Next_Animal := Animal_ID'First;
            end Get_Next_Animal;
      
          end loop;
        end Fill;
      
      end Zoo;
   
      use Zoo;
   
      CR : Cage_Row;
   
   begin
      Fill (CR);
   end;

Ken has been a little coy with this example, but his original post and a
followup strongly suggest that there is some mysterious justification for
the above code failing to work in the obvious manner.

The obvious expected output is (this comes out of GNAT):

  Cage  1 contains animal  1
  Cage  2 contains animal  2
  Cage  3 contains animal  3
  Cage  4 contains animal  4
  Cage  5 contains animal  5
  Cage  6 contains animal  1
  Cage  7 contains animal  2
  Cage  8 contains animal  3
  Cage  9 contains animal  4
  Cage  10 contains animal  5
  Cage  11 contains animal  1
  Cage  12 contains animal  2

No other output is correct, and on the face of it, no one would normally
think that this code is other than completely correct. Of course the
assignment:

   Next_Animal := Animal_ID'Succ(Next_Animal);

raises CE if the value is out of range, and the hander is entered, and
Next_Animal is set back to the First value.

So what is all the commotion about. From Ken's posts, the best guess is
that he has used a compiler with a serious bug in it that omits the
constraint check in this case (I say serious, because any bug that results
in unexpected wrong results is serious, much worse say than a blowup at
compile time). If this guess is correct, then Ken, please report the bug
to the compiler vendor.

That really is the end of the discussion, but since followup posts have
wandered off trying to justify some kind of incorrect execution. Let's
dig a little further. Those who just want their programs to work can
ignore this, and rest assured that Ken's quoted code is of course fine.

Let's look at the statement in question more closely:

   Next_Animal := Animal_ID'Succ(Next_Animal);

The call to Animal_ID'Succ (Next_Animal) of course does not raise an
exception, no more than Next_Animal  + 1 would. Arithmetic computations
work always with values of the base type -- this is pretty fundamental.
Ken commented that this is counter-intuitive, but it should not be.
If this seems counter-intuitive, then you don't quite have the right
view of subtypes and base types. Subtype constraints in effect apply
to objects, and limit what can be stored. Yes, in a few places, notably
conversions, range checks happen in expressions, but arithmetic, including
the normal arithmetic operators and Succ and Pred, always work with the
base type.

For example, it is quite fine to write

   Next_Animal := (- Next Animal) + 4;

even though -Next_Animal is of course out of range of the constraint for
Animal_ID. The only requirement is that the result be in range for the
assignment.

That requirement also applies to Ken's statement, so of course the assignment
raises constraint error when the value to be assigned to Next_Animal is out
of range of the subtype.

Note that in a screwy implementation with a base type with range -5 .. 5
which was used as the base type for this declared type, the program would
still work, though in that case it is the Succ call that would cause the
constraint error. This is a subtle distinction which most users would
not think about, and do not need to think about, since the result is a
Constraint_Error in either case.

So, the mystery is, why is this a brain teaser for Ken. That must mean that
he found it not working on some compiler. Well that's a bug! It should be
reported as such.

Ken, if you have run into such a bug, then I would guess that the compiler
implementor has made the mistake of assuming that the result of X'Succ is
always in range of X, which is of course wrong in this case, and thus thinks
they can get away with omitting the range check.

Now Ken, can you elucidate why you presented this example?

Now, since this works, in both Ada 83 and Ada 95 in the expected manner, the
mystery is why

However, the assignment to Next_Animal most certainly must raise a
constraint error if the value is out of range. I can imagine a compiler
having a bug here and failing to do the required constraint check (the
way this happens is if the implementor gets the handling of Succ right,
which is likely, there are ACVC tests), but gets confused into thinking
the constraint check can be omitted when it cannot in this case.

Remember that ALL assignmments have constraint checks, even A := A requires
a constraint check from a semantic point of view in Ada 83. It is just that
you can omit it in some cases (including A := A, but not including the
example above).

So the bottom line is that the code is fine, and if it does not work,
you have found a bug, you should report it. There is no change from
Ada 83 to Ada 95 that would affect the correct functioning of this
code.

-------------- replies to some follow on messages ------------

From Ken:

  "That certainly seems the intuitively obvious answer to me. Does anyone vote
  for a row of cages filled with 1 .. Cages'Last?"

No one should vote for that alternative, it is clearly wrong. If you have
a compiler that votes for that alternative, the compiler is wrong!

---------
From Ted:
---------

  "returns a value in the range of the BASE type of Animal_ID, which is the
  range of whatever predefined type the compiler picked above. However,
  when this value is assigned back into Next_Animal, it is subject to a
  constraint check on the 1..5 range. The constraint check doesn't HAVE to
  happen, but it could."

Yes, it does have to happen. You have been fooled into thinking otherwise
by the original question, which incorrectly implied that the code did not
work as expected.

---------
From Ted:
---------

  "However, I should (and did) point out that the constraint check does
  NOT have to happen (and does not have to happen within the begin block).
  That is why Ada (95) now has the 'valid attribute."

That's very confused and is wrong. Of COURSE constraint checks are required
if the variable on the left side of an assignment is of a subtype with
a constraint.

Of COURSE this fundamental requirement has not changed in Ada 95.

The 'Valid attribute has nothing at all to do with the case, it is for use
in cases where by various means you manage to get an abnormal or undefined
value into a variable (e.g. it is uninitialized, set by a bad unchecked
conversion, read from a stream, etc.)

But assignment can never leave an abnormal result in the absence of a
raising of an exception. In our case here:

           Next_Animal := Animal_ID'Succ(Next_Animal);


in practice, the right side evaluation will NOT raise a CE. It is if
you are being fanatically formal allowable to raise CE during evaluation
of the right side (if Animal_Id'Base'Last = 5), and that CE, if you are
being fanatically formal, could leave Next_Animal with an abnormal value,
but in any case the exception would be raised and Next_Animal is reset
in either case.

This example is straightforward and works fine. As I suggested in a previous
posting, I suspect that some compiler bug has given rise to this whole
thread of confusing speculations!

---------
Bob said:
---------

  "Right, so depending on the particular (Ada 83) compiler, the code may or
  may not produce the desired result, depending on whether the constraint
  check is performed within the begin block or not."

Nope, this is wrong. The code will always work. There is no authorization
for moving this constraint check out of the frame.

---------
Ted said:
---------

"Maybe, maybe not. I see three valid possibilities -

   1 - It raises an exception at the assignment. (This is what the code
       seems to be assuming)

   2 - No exception is ever raised (Constraint checking could be disabled
       for any number of reasons).

   3 - A CONSTRAINT_ERROR is raised OUTSIDE the block, upon the next attempt
       to use the value of Animal_ID. (That's is what I meant by "within this
       block" above).
"


Possibility 1 is certainly correct
Possibility 3 is definitely NOT correct. The reason I am reemphasizing
 this is that to me the suggestion that this might not work and cannot
 be relied on may be very unsettling to those who do not know Ada well
 enough to be confident that statements like this are false. Of COURSE
 the CE is caught by the handler, you can move CE's around a bit, but
 not outside the frame. Since we are talking Ada 83 here (though Ada 95
 is no different), let's quote from the Ada 83 RM, 11.6(4) says

  second, for each operation, the innermost enclosing frame or accept
  statement mnust be the same in the alternative order as in the canonical
  order, and the same exception handlers must apply.

As to possibility 2, yes, certainly if you suppress constraint errors
this code will not work.

In GNAT, we have introduced a pragma Unsuppress, which undoes the effect
of any Suppress (either from the command line switch or from a pragma
Suppress). We use Unsuppress to emphasize that we are relying on certain
checks for correctness of the code (if you want to see some examples of
this use, look in the file a-tiinio.adb -- the body of Integer_IO).

I hope this clears things up. One suggestion I have in this situation is
to run code like this with GNAT. Then if the GNAT output surprises you,
investigate further, since GNAT is not always right, but it is also not
always wrong, and it is quite likely that when GNAT surprises you, GNAT
is right and you are wrong. If we look at all the reports that are sent
to us, many identify bugs in GNAT which we fix, but a far larger number
are confusions of expectations on the part of the person asking the
question. 





  parent reply	other threads:[~1996-08-15  0:00 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
1996-08-12  0:00 Zoo question Ken Garlington
1996-08-12  0:00 ` Chris Morgan
1996-08-13  0:00   ` Ken Garlington
1996-08-13  0:00 ` Ted Dennison
1996-08-14  0:00   ` Ken Garlington
1996-08-14  0:00     ` Ted Dennison
1996-08-15  0:00       ` Ken Garlington
1996-08-18  0:00         ` Robert Dewar
1996-08-19  0:00           ` Ted Dennison
1996-08-19  0:00             ` Mark A Biggar
1996-08-20  0:00             ` Robert Dewar
1996-08-15  0:00       ` Robert A Duff
1996-08-15  0:00     ` Robert Dewar [this message]
1996-08-15  0:00       ` Bob Gilbert
1996-08-19  0:00         ` Ted Dennison
1996-08-16  0:00       ` Ken Garlington
1996-08-19  0:00       ` Ted Dennison
1996-08-15  0:00   ` Keith Thompson
1996-08-14  0:00 ` Paul Hussein
1996-08-14  0:00 ` Bob Gilbert
1996-08-14  0:00   ` Ted Dennison
1996-08-14  0:00     ` Bob Gilbert
1996-08-14  0:00       ` Ted Dennison
1996-08-14  0:00         ` Bob Gilbert
1996-08-14  0:00           ` Ken Garlington
1996-08-15  0:00             ` Robert A Duff
1996-08-16  0:00             ` Bob Gilbert
1996-08-19  0:00               ` Ted Dennison
1996-08-25  0:00                 ` Robert Dewar
1996-08-15  0:00           ` Robert I. Eachus
1996-08-15  0:00 ` John Herro
1996-08-16  0:00   ` Robert Dewar
1996-08-18  0:00     ` John Herro
1996-08-19  0:00       ` Ken Garlington
replies disabled

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox