From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,74a56083ffbe573d X-Google-Attributes: gid103376,public From: dewar@cs.nyu.edu (Robert Dewar) Subject: Re: Zoo question Date: 1996/08/15 Message-ID: X-Deja-AN: 174363531 references: <320F16B6.6944@lmtas.lmco.com> <3210A142.2781E494@escmail.orl.mmc.com> <3211C462.19D9@lmtas.lmco.com> organization: Courant Institute of Mathematical Sciences newsgroups: comp.lang.ada Date: 1996-08-15T00:00:00+00:00 List-Id: 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.