5.6 Arrays as Parameters

As indicated by two of the examples in the last section, entire arrays (or their individual elements, as appropriate) may be passed to procedures as parameters. As in all other such cases, an actual value parameter passed must be assignment compatible with the formal value parameter found in the procedure declaration. If using variable parameters, the types of the actual and formal parameters must be the same.

These limitations are not onerous in most cases, but by themselves they would prevent the writing of general-purpose code in some situations. For instance, suppose one had two arrays of the same type and wished to add them element by element and place the sum in a third array. That is, given the declarations:

CONST
  size = 3;
TYPE
  Vector = ARRAY [0 .. size - 1] OF CARDINAL;
VAR
  vectA, vectB, ansVector : Vector;

and the assignments

  vectA [0] = 5; vectA [1] = 3; vectA [2] = 7;
  vectB [0] = 4; vectB [1] = 8;  vectB [2] = 10;

and, one wished to end up with:

ansVector [0] <-- 9, ansVector [1] <-- 11, and ansVector [2] <-- 17

one could write a routine procedure like:

PROCEDURE AddArrays (firstVector, secondVector : Vector; VAR resultVector : Vector);
VAR
  count : CARDINAL;

BEGIN
  FOR count := 0 TO size - 1
    DO
      resultVector [count] := firstVector [count] + secondVector [count];
    END;     (* for *)
END AddArrays;

This procedure would be called as usual by including the line AddArrays (vectA, vectB, ansVector) in the main program. However, suppose there were two vector types in the main program, one of length three, and one of length two.

TYPE
  Vector2 = ARRAY [0 .. 1] OF CARDINAL;
  Vector3 = ARRAY [0 .. 2] OF CARDINAL;
VAR
  vect2A, vect2B, ansVector2 : Vector2;
  vect3A, vect3B, ansVector3 : Vector3;

In order to add the vectors of type Vector2, one would have to have a different procedure than the one above for adding vectors of type Vector3, because the vectors to be added must be passed to the formal parameter of a compatible type, and a vector of two components is clearly not compatible with one of three components. There is nothing to assign to the third component.

However, there is a way to make the above code more generic or multi-purpose in Modula-2 by using an array parameter that does not specify the length of the array formally, but calculates the number of components being used when an actual assignment to the parameter is made. For instance, the above procedure would be re-written as:

PROCEDURE AddArrays2 (vect1, vect2 : ARRAY OF CARDINAL;
                             VAR ansVector : ARRAY OF CARDINAL);
VAR
  count: CARDINAL;

BEGIN
  FOR count := 0 TO HIGH (vect1)
    DO
      ansVector [count] := vect1 [count] + vect2 [count];
    END;
END AddArrays2;

and this could be used to add vectors of either type discussed here, or of any similar type defined as ARRAY range OF CARDINAL.

NOTES: 1. The anonymous type ARRAY OF CARDINAL replaces ARRAY range OF CARDINAL or a named type in the formal declaration.

2. When the procedure is called, any actual parameter that is an ARRAY range of CARDINAL is assignment compatible with the formal parameter.

3. Within the operating procedure, the actual range of the parameters is always [0 .. HIGH (parameter)].

An open array is an array of some base type without any range specified. It is written as ARRAY OF type and its implied definition is: ARRAY [0 .. HIGH (name)] OF someType.
HIGH is a standard function procedure that computes the highest cardinal index employed when the actual parameter was assigned upon the procedure being invoked.

All three arrays passed to this particular procedure ought to be of the same base type, or else the use of HIGH applied only to one of them would be rather misleading. This stipulation should be added to the code as a precondition in the form of a comment. If there were any possibility that this might not be the case, some action would have to be undertaken. There are two possibilities:

PROCEDURE AddArrays3 (vect1, vect2 : ARRAY OF CARDINAL;
                             VAR ansVector : ARRAY OF CARDINAL);
(* adds the first two arrays and places the result in the third component by component
Pre: none
Post: If all three vectors are the same size then the third is the sum of the first two.  
All components of any of the vectors that is longer than any of the others are ignored. An overflow error may be generated on an individual component at run time if the values being added are too large. *) VAR count, size : CARDINAL; BEGIN (* pick the minimum of the three sizes *) size := HIGH (vect1); IF HIGH (vect2) < size THEN size := HIGH (vect2); END; IF HIGH (ansVector) < size THEN size := HIGH (ansVector); END; FOR count := 0 TO size DO ansVector [count] := vect1 [count] + vect2 [count]; END; END AddArrays3;

This approach detects an error and deals with it by ignoring it and computing as much of a sum as is meaningful under the circumstances. A second approach detects and reports the error, and refuses to do the sum, leaving it to the calling code to decide what to do about the error:

PROCEDURE AddArrays4 (vect1, vect2 : ARRAY OF CARDINAL;
                             VAR ansVector : ARRAY OF CARDINAL;
                             VAR addOk : BOOLEAN);
(* adds the first two arrays and places the result in the third component by component
Pre: none
Post: If all three vectors are the same size then the third is the sum of the first two and addOk returns true.  
If any of the vectors is longer than any of the others, addOk returns false and ansVector is undefined. An overflow error may be generated on an individual component at run time if the values being added are too large. *) VAR count, size : CARDINAL; BEGIN size := HIGH (vect1); IF (size # HIGH (vect2)) OR (size # HIGH (ansVector)) THEN addOk := FALSE; (* bad data; don't do addition *) ELSE FOR count := 0 TO size DO ansVector [count] := vect1 [count] + vect2 [count]; END; addOk := TRUE; END; END AddArrays4;

Here are two first procedures from section 5.5 modified to use open arrays:

PROCEDURE Init2 (VAR theArray : ARRAY OF INTEGER);
(* sets all elements of the array passed to zero *)

VAR
  count : CARDINAL;

BEGIN
  FOR count := 0 TO HIGH (theArray)
    DO
      theArray [count] :=0;
    END;
END Init;

PROCEDURE Add (VAR theArray : ARRAY OF INTEGER) : INTEGER;

VAR
  count: CARDINAL;
  sum : INTEGER;

BEGIN
  sum := 0;
  FOR count := 0 TO HIGH (theArray)
    DO
      sum := sum + theArray [count];
    END;
  RETURN sum;
END Add;

NOTES: 1. If an ARRAY OF CHAR is passed an empty literal string, HIGH would return zero.

2. Within the procedure, no assignment can be made to the entire open array. Its type is anonymous, so such entities are incompatible with anything else when taken as a whole. One can assign to it only element by element. So, ansVector := vect1 would be illegal if ansVector were an open array.

HIGH always returns a cardinal value. It is quite proper to assign any actual parameter array of the correct type, however indexed, to an open array. However, in the procedure, the indexing will be by cardinal values starting at zero, and not by the values of the formal index parameter type. Thus, given the declarations

  TYPE
    Days = (Mon, Tue, Wed, Thu, Fri);
    PayList  = ARRAY [Mon .. Fri] OF REAL;
    OddVector = ARRAY [-3 .. 4] OF REAL;

  VAR
    pay : PayList;
    theVector : OddVector;

  PROCEDURE FindMax (theReals : ARRAY OF REAL) : REAL;
  (* appropriate code here *)

the procedure FindMax can be invoked in the program with either pay or theVector as actual parameter. Inside the procedure, the formal parameter is indexed starting at zero in both cases. HIGH (theReals) would therefore produce 4 in the former case, and 7 in the latter.

In the examples given thus far, all arrays being returned by procedures have been handled as variable (or in-out) parameters. Modula-2 also permits function procedures to return an array.

  TYPE
    RVector = ARRAY [0 .. 1] OF REAL;

  PROCEDURE AddRVector (v1, v2 : RVector) : RVector;

  VAR
    z : RVector;

  BEGIN
    z [0] := v1 [0] + v2 [0];
    z [1] := v1 [1] + v2 [1];
    RETURN z;
  END AddVector;

NOTE: The type RVector contains reals, and is therefore not compatible with an open array containing type cardinal as used in previous examples.

It is not permitted to use the syntax of an open array anywhere except in the parameter list of a procedure. Thus the following are both

Incorrect:

  TYPE
    openArray = ARRAY OF CARDINAL;
  PROCEDURE AddArray (v1, v2 : ARRAY OF REAL) : ARRAY OF REAL;

WARNING: In most cases when an array is passed as a parameter, and the array itself is not being changed, one would use a value parameter (passing data in only). However, arrays can consume a large amount of memory. If several arrays are passed as value parameters, the space each takes is doubled while the procedure is active. In some such cases, it might be better to use variable parameters even though the data is not in/out. This practice also saves the time taken to make the extra copies when the procedure is entered, and this time may be substantial if the array is large.


Contents