Sunday, September 2, 2012

AdaTutor - Subprograms and Packages (2)

Packages

A package lets us group related declarations, procedures, and functions.  A program can with the package and gain access to all of these.  However, as we'll see, packages have many other advantages.  Usually library units are packages rather than individual procedures and functions.

By way of example, consider a very simple procedure and a very simple function:

   procedure Double(Number : in Integer; Answer : out Integer);
   function Twice(Number : in Integer) return Integer;

We could compile these individually, or we could put them in a package and compile that instead.  Here's the package specification:

   package Simple_Math is
      procedure Double(Number : in Integer;
                       Answer : out Integer);
      function Twice(Number : in Integer) return Integer;
   end Simple_Math;

The package body must contain the bodies of all the procedures and functions declared in the package specification:

    package body Simple_Math is
        procedure Double(Number : in Integer;
                         Answer : out Integer) is
        begin
            Answer := Number * 2;
        end Double;

        function Twice(Number : in Integer) return Integer is
        begin
            return Number * 2;
        end Twice;
    end Simple_Math;

The package body could optionally declare either or both subprograms to be separate:

    package body Simple_Math is
        procedure Double(Number : in Integer;
                         Answer : out Integer) is separate;
        function Twice(Number : in Integer) return Integer
          is separate;
    end Simple_Math;

Here's an example of a calling program that withs the package and makes use of the two subprograms declared in the package specification:

 with Simple_Math; use Simple_Math;
 procedure Package_Demo is
   I, J : Integer := 10;
 begin
   Double(Number => I, Answer => J);  -- This line sets J to 20.
   J := Twice(I);                -- This line also sets J to 20.
 end Package_Demo;

The package specification must be compiled first, but either the calling program or the package body may be compiled second.  The calling program depends only on the package specification, not the package body.  Similarly, the package body depends on the package specification.  If the package body declares any subprograms to be separate, these must be compiled after the package body, because any separate subprogram depends on its "parent."

A package specification that declares no subprograms (only objects, types, etc.) doesn't need a package body.  In Ada 95, a package specification that doesn't need a body may not have one, unless we include pragma Elaborate_Body; in the package specification.  We'll learn more about pragmas later.

The main program may never be inside a package.  It must be a procedure or function compiled directly into the library, such as Hello or Add.  In most implementations of Ada, the main program can't have any parameters, and must be a procedure rather than a function.

A package body may optionally have initialization code, introduced by the word begin.  This code is executed only once, the first time another program elaborates the package by withing it.  For example, this package uses initialization code to initialize the array B:

   package P is
      function F return Float;
   end P;

   package body P is
      A : array(1 .. 10) of Float := (others => 0.0);
      B : array(1 .. 10, 1 .. 10) of Integer;
      function F return Float is
         ...
      end F;
   begin
      for I in 1 .. 10 loop
         for J in 1 .. 10 loop
            B(I, J) := I*J*J;
         end loop;
      end loop;
   end P;

Declarations made inside a package specification are said to be exported and are available to programs outside the package that with the package.  However, declarations made inside the package body are available only inside the package.  The package body doesn't even have to be written before outside programs that with the package.  In this example, programs outside and inside the package may use type Answer, array A, and procedure R, but only procedures P, Q and R may use type Question and array B.  Procedure P may be called only by Q and R (and itself), and procedure Q is available only to R (and itself).

   package X is
      type Answer is (Yes, No, Maybe);
      A : array(1 .. 10) of Float;
      procedure R;
   end X;

   package body X is
      type Question is (Why, Who, How);
      B : array(1 .. 10) of Integer;
      procedure P is separate;
      procedure Q is separate;
      procedure R is separate;
   end X;

Question

True or False?  In the above example, porcedure P may call R.

Functions with Infix Notation

                  +   -   *   /   **   &   =   <   >   <=   >=

                     and   or   xor   abs   not   mod   rem

Ada allows us to overload any of the above operators by enclosing the operator in quotes following the word function.  For example, having defined type Date, we may want to define what it means for one date to be "less than" another.  We could write function "<"(Left, Right : in Date) return Boolean;.  Then, our program could declare D1, D2 : Date; and test D1 < D2.  This test would call our function "<", because < is used between two objects of type Date.  Similarly, we can overload "*" to give the dot product of two vectors:

   type Vector is array(Integer range <>) of Float;
   V1, V2 : Vector(1 .. 100);
   X : Float;
   function "*"(Left, Right : in Vector) return Float is
      ...
   end "*";
   ...
   X := V1 * V2;  -- This calls our function "*".

There are some restrictions when using infix notation.  First, all of the operators above (except abs, not, +, and -) must be used between two expressions, and thus the function specification must have exactly two parameters.  Traditionally, the two parameters are called Left and Right.  The operators abs and not must have just one expression on the right, and + and - may have one expression on the right, or come between two expressions.  These restrictions come from the way the compiler handles operators.  For example, the compiler can handle X := - X;, but it's not designed to handle X := * X;.

Second, in Ada 83 the function "=" must return type Boolean, its two parameters must be of the same type, and that type must be a limited private type, to be discussed later.  For all the types discussed so far, function "=" is already defined by Ada.  Two records are equal if all of their corresponding fields are equal, and two arrays are equal if they have the same length and all of their corresponding elements are equal.

Note that in Ada 83 we can't redefine function "/=". However, if we redefine "=" for some limited private type, we can use /=.  Ada will call our function "=" and negate the result. For example,

   type Text is limited private;  -- to be discussed later
   T1, T2 : Text;
   function "="(Left, Right : in Text) return Boolean is
      ...
   end "=";
   ...
   if T1 /= T2 then  -- Calls our "=" and reverses the result.
      ...
   end if;

Also, in Ada 83 and Ada 95, we can't redefine in, not in, and then, or else, or :=.  Technically, these aren't operators.

In Ada 95, we may redefine =to return any type, not just Boolean.  However, if we do redefine = to return type Boolean, then we have automatically also redefined /= to call our = and reverse the result.

If, on the other hand, we redefine = to return some type other than Boolean, then /= is not automatically redefined.&mnsp; However, in this case Ada 95 lets us separately redefine /= if we want to, and it may return any type.

Functions using infix notation can't be compiled directly into the library.  They must either be declared locally inside a procedure or function, or placed inside a package.  This is done because many implementations of Ada create files with names based on the name of the function, procedure, or package being compiled.  Since many of the operators are punctuation marks, they would create file names that are illegal on most systems.

For the same reason, two functions or procedures with the same name can't be compiled directly into the library; on many systems that would attempt to give the same name to two different files.  For example, the package specification below is legal.  However, the package body can't declare both Display's separate, and it can't declare either "*" separate.

   package P is
      type Complex is ...
      type Vector is ...
      procedure Display(C : in Complex);
      procedure Display(V : in Vector);
      function  "*"(Left, Right : in Complex) return Complex;
      function  "*"(Left, Right : in Vector)  return Float;
   end P;

Question

   package K is
      type Complex is record
         Re, Im : Float;
      end record;
      type Vector is array(Integer range <>) of Float;
      function  "+"(Left, Right : in Complex) return Complex;
      function  "*"(Left, Right : in Complex) return Complex;
      function  "*"(Left, Right : in Vector)  return Float;
      function  Conjugate(C : in Complex) return Complex;
      procedure Display(C : in Complex);
      procedure Display(V : in Vector);
   end K;
The above is a package specification.  In the package body, how many subprograms could be made separate: 0, 1, 2, 3, 4, 5, or 6?

< prev   next >

No comments: