There is a darned Ada83 rule forbidding reading formal out-parameters. You often find some contorted code to cope with this restriction - even in cases where it is absolutely unnecessary.
Suppose you have a bunch of documents (a discriminated record) you want to store. So you declare the following package:
package Archive is type Key_Template is (Desk, Safe, ...); type Document (Key: Key_Template := Desk) is record case Key is when Desk => Confidential: ...; when Safe => Secret : ...; when ... end case; end record; procedure Store (Letter: in Document); procedure Retrieve (<Parameters>); end Archive; package body Archive is Storage: array (Key_Template) of Document; procedure Store (Letter: in Document) is begin Storage (Letter.Key) := Letter; end Store; procedure Retrieve (<Parameters>) is begin ? end Retrieve; end Archive;
Now our poor programmer is brooding about how to define the retrieve operation.
procedure Retrieve (Letter: in out Document) is begin Letter := Storage (Letter.Key); end Retrieve;
which has the wrong mode in out (it's a pure out parameter), or
procedure Retrieve (use_Key: in Key_Template; Letter: out Document) is begin Letter := Storage (Use_Key); end Retrieve;
which is unnecessarily tedious because of the first parameter use_Key. Now if you ask why all these contortions instead of the simple and correct and symmetric
procedure Store (Letter: in Document); procedure Retrieve (Letter: out Document);
you will undoubtedly and invariably get the answer: "But you can't read out-parameters."
Of course you can, at least some parts - of arrays, you may read the limits, of records, you may read the discriminants and the limits and discriminants ot their components, ARM_83 6.2 (5).
Why is this so? Well, how else should the following ever work?
Text: String (20 .. 50); Text_IO.Get_Line (Text (21 .. 30), Last);
Everyone knows Text_IO. Here Text is the actual parameter for the formal out-parameter Item, and in the body of Get_Line, something like the following happens:
for C in Item'Range loop Item (C) := Next_Character; end loop;
The attributes Item'First , Item'Last , Item'Range may be used to read the limits [the values for our example are given in brackets].
Thus the solution for our key problem is
procedure Retrieve (Letter: out Document) is -- correct mode begin Letter := Storage (Letter.Key); -- read the discriminant end Retrieve;
and we empty the safe like so:
declare Contents: Document (Safe); begin Retrieve (Contents); end;
When however nothing has ever before been stored in the safe, we get Constraint_Error. Have we forgotten something?
The Storage variable is uninitialised. Therefore for each entry the same variant (the default Desk) is stored and the discriminant check fails.
Now initialisation is apt to get very tedious as we shall see presently if not done in a smart way. First we add a component denoting whether there is stored anything at all.
type Document (Key: Key_Template := Desk) is record Empty: Boolean; ... -- Rest as before end record;
The package body gets an executable part (executed during elaboration):
package body Archive is – Declarative part as before begin for Key in Key_Template loop case Key is when Desk => Storage (Key) := (Key => Desk, Empty => True, Confidential => ...); when Safe => Storage (Key) := (Key => Safe, Empty => True, Secret => ...); when ... => … end case; end loop; end Archive;
To our dismay, with this method in a way we have to repeat the whole record declaration. If we have many variants, this is getting really awkward.
Fortunately all this is unnecessary if we add the correct default values to the record declaration:
type Document (Key: Key_Template := Desk) is record Empty: Boolean := True; ... -- Rest where applicable with appropriate default values end record;
Initialising now is really simple.
for Key in Key_Template loop declare Letter: Document (Key); -- Letter: constant Document (Key) := ???; begin Storage (Key) := Letter; end; end loop;
Note the variable declaration without an initial value – a constant would need an initial value and we were back at the problem how to denote it. (By the way: Declarations without initial value are strictly disallowed by some coding standards, especially when the variable is never assigned a value, as is the case here! But as we have seen, sometimes seemingly obvious nonesense is unavoidable and – if done in the correct way – perfectly legal.)