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 [21], Item'Last [30], 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.)
|