AdaPower Logged in as Guest
Ada Tools and Resources

Ada 95 Reference Manual
Ada Source Code Treasury
Bindings and Packages
Ada FAQ


Join >
Articles >
Ada FAQ >
Getting Started >
Home >
Books & Tutorials >
Source Treasury >
Packages for Reuse >
Latest Additions >
Ada Projects >
Press Releases >
Ada Audio / Video >
Home Pages >
Links >
Contact >
About >
Login >
Back
Reading Out Parameters in Ada83 (Christoph Karl Walter Grein)

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.)


(c) 1998-2004 All Rights Reserved David Botton