9.8 Using Records

Most people come to the conclusion while working through reading the last section that writing "student." in front of every record field identifier when doing assignments is a pain in the neck. The obvious thing to wonder, in view of previous experience, is whether the qualified identifiers of record fields can be unqualified in some fashion similar to the unqualifying of identifiers from module imports (in a FROM statement). They can, and this unqualifying is done as in the construction below:

WITH student
  DO
    lastname := "Hacker";
    firstname := "Nellie";
    birth.year := 1965;
    birth.month := Feb;
    ...

  END;

Within the boundaries of the WITH statement, a new scope is created in which the record fields of the record named in the WITH can be used unqualified. Here is the diagram of the WITH statement:

In a sense, the WITH statement serves a similar role in selecting the fields of a record (for assignment or otherwise) as does the FOR to select the indices of an array. In another sense, its effect on the identifiers within its scope is to unqualify them in the same manner as does the FROM statement. Naturally, one can nest these and further compact the above in this manner:

WITH student
  DO
    lastname := "Hacker";
    firstname := "Nellie";

    WITH birth
      DO
        year := 1965;
        month := Feb;
        day := 12;
      END;

    male := FALSE;
    idnumber := 46725;
    married := FALSE;
    status := freshman;
  END;

NOTES: 1. Only one identifier can be named in each WITH statement.

2. Within a WITH statement, no assignment can be made to the identifier it names. It will not work to try to say

student := otherStudent

within this WITH statement, for the variable unqualified is evaluated only once, when the scope of WITH is entered, and this value is the one referenced throughout that scope. This is similar (not the same as) to the rule that one cannot re-assign the index variable of a FOR within its loop.

3. Each WITH has its own END.

4. Any fields not assigned are indeterminate and could contain any random data.

Naturally, one can read data from the keyboard and assign it directly to the variable parameters of the read statements from an appropriate library module:

WITH student
  DO
    WriteString ("Enter the last name please. ");
    ReadString (lastname);   (* assigns student.lastname *)
    ...
  END;

Once data has been assigned to a field in a record, it can subsequently be manipulated in the same way as any other data. Individual fields of a record can be used as part of numerical expressions in assignments (where appropriate) and whole records with all their fields can be assigned (provided they are of the same type). Starting with:

TYPE Employee =
  RECORD
    lastname, firstname : Name;
    salary : REAL;
  END;

VAR
  clerk, secretary : Employee;

and assuming the appropriate assignments have been made to the fields and that the other identifiers used below were also defined, one could write assignments like:

secretary := clerk;   (* someone was transferred *)

but, one cannot write statements like:

IF secretary = clerk
  THEN   (* comparisons not allowed *)
    pay := hours * clerk.salary
  END;

NOTE: If one of the fields were unassigned, or contained unassigned elements, such as the unused trailing portions of a string (or other array components), a comparison employing = or # would not give meaningful results, even if the data in the used fields and components is all equal. For this reason, such comparisons are not allowed.

Two points are worth considering when deciding whether or not to employ the WITH statement. On the one hand, some people consider it to be more trouble than it is worth to unqualify using WITH for the purpose of making a single assignment. On the other, there are those, including some teachers and language experts, who believe that WITH should never be used, on the grounds that a program is easier to read when all identifiers are employed in their qualified form. (For consistency, such purists will also only import qualified identifiers.)

Second, if one imports a record into a module, one automatically imports qualified all its field names along with it. This means that the field identifiers need not be specifically mentioned in the import statement, only the type name or variable name under whose umbrella they are declared. This is a similar rule to the one that was earlier applied to the import of enumerated types, which implied an automatic import of all the identifiers in the enumeration. The difference is that field type names are only available in qualified form unless WITH is employed to open a new scope, whereas enumeration item names are available unqualified.

Example:

Supposing that in the module Files there were a variable FilePos defined whose structure was:

TYPE
  FilePos =
    RECORD;
      hi, lo : CARDINAL
    END

and then, in some program module there was the code:

FROM Files IMPORT
  FilePos;

then the following code would be correct:

PROCEDURE WritePos;
BEGIN
  WriteCard (FilePos.hi, 6);
  WriteCard (FilePos.lo, 6);
END WritePos;

Because the record name has been imported, its field names are also visible in the importing scope as qualified identifiers, and can also be unqualified with an appropriate WITH statement.


Contents