10.5 Local Modules--Scope and Visibility Rules

As mentioned in passing in section 6.8, modules may themselves contain other (nested) modules and the collection consisting of these local modules together with the (global) program module is known as the program library.

A local module is any module entirely contained within some compilation unit.

Local modules resemble procedures in that they define a scope of visibility for the entities they contain. They do not, however have parameters, nor necessarily execute code. Such constants, variables or procedures may in turn be made visible in the scope of the surrounding module by naming them in an EXPORT statement. Conversely, they can be brought into a module from the surrounding scope by naming them in an IMPORT statement. It is important to note that module entities are not automatically visible in scopes created inside the ones in which they are declared as is the case for entities declared in or viewed from a procedure.

Visibility of entities declared in procedures is inherited hierarchically and automatically inward with increasing levels, but visibility of entities declared in modules must be explicitly and manually controlled.

Figure 10.6 illustrates the use of EXPORT and IMPORT in a simple case and lists the resulting visibilities in the two scopes.

As in the case of Inner, a module does not have to have a body with statements that actually execute (it need only be a collection of entities), but it does have to have an END even if it there is no BEGIN. If it has a body, then that statement sequence will be executed at the time the main module containing it is activated. That is, at the same time that its entities come into existence, there is an opportunity to initialize them if the programmer so wishes. This applies to local as well as to library modules.

The body of a module may be referred to as its initialization section.
NOTE: Remember to initialize any module entities in its body if you are counting on them to have some value the first time they are called. This applies mainly to variables, including procedure variables, as they will be the ones used most commonly in this fashion.

It is important to realize that the wall erected around entities by a MODULE framework affects only the visibility of entities in the scopes on either side of the wall. It does not affect their existence between calls to procedures inside the module wall. Therefore, variables declared in a module (as opposed to one of its procedures) retain their values after the module is exited. This is quite different from the effect of declaring a procedure, for in that case variables declared inside are neither visible nor do they exist outside it. An entity is visible within procedure scopes created inside the one where it is defined, but not within module scopes created in this way, as the above example also illustrates. These rules apply to any combination of procedures and modules and the ones in which they are contained. See the next section for an illustration.

Observe that visibility is really only of concern at compile time, whereas existence is a run-time phenomenon. Since all variables declared at the outermost blocks of modules exist at run-time, one could even say that at run-time modules, unlike procedures, do not exist at all.

The net result of the module visibility rules is a further reduction in the number of global variables necessary, because the visibility of variables to any portion of the program can be strictly controlled with the IMPORT and EXPORT lists. This limits side effects, increases flexibility and improves the readability of programs. For instance, not only will a variable defined in a module keep its value until the next call to the module, it can be made visible outside the module without having to have (another) global name, whereas a procedure must pass a value to a global variable to communicate to the outer scope.

This can even be seen in the use of variables imported from library modules. One example is the boolean variable Done that is imported from the classical library InOut. One does not need to set and/or fetch the value of this variable whenever one does a read operation because its value is set by InOut itself whenever that module is called upon for an input operation. Consequently, the current value of Done is always available, once one has mentioned it in an import statement. On the other hand, the global availability of Done implies that it could be changed by other operations in the client program unrelated to read attempts, thus corrupting its meaning. That is why the ISO library makes available the enquiry function ReadResult to return a hidden error value instead. The hidden value cannot be changed outside the separate library module that owns it.

In any event, certain rules must be rigidly followed in the import and export of variables to and from modules. First, these lists must immediately follow the MODULE heading. There can be as many IMPORT statements as desired but only one EXPORT statement, and it must be the last of these lists.

MODULE MyModule;
IMPORT variable1, variable2;
IMPORT variable3;
EXPORT myVariable1, myVariable2;

(* declaration section comes next *)

Second, care must be taken to avoid an identifier clash. If two modules both export the same identifier into a common scope of visibility, then that entity will have been erroneously re-defined, and the compiler will report an error. For instance, module A and module B cannot both simply export a variable number into the surrounding module D, nor can module D import items named number into its own scope from two different sources.

To avoid such clashes when exporting, one can write:

MODULE Outer;

MODULE Show;
EXPORT QUALIFIED item, number, sum;
   etc.

When this is done, references in the surrounding scope must refer to the items by a qualified identifier. As in previous instances, this is written in the scope of module Outer as Show.Item, Show.A, etc. Now, a clash occurs only if one tries to give two modules the same name, and this problem should be easier to find and solve.

NOTE: Even if the EXPORT is not qualified, the entity may be referred to in the new scope with a qualified identifier, though this is unnecessary.

MODULE CheckModVisibility;

MODULE Inner;
EXPORT number;
VAR
  number : REAL;

END Inner;

BEGIN
  number := 5.0;
  Inner.number := 9.8;  (* both references legal *)
END CheckModVisibility.

MODULE CheckModVisibility2;

MODULE Inside1;
EXPORT number1;
VAR
  number1 : REAL;

BEGIN  (* Only number1 is visible here *)
  number1 := 5.4; (* number1 can be initialized here *)  
END Inside1;

MODULE Inside2;
IMPORT Inside1;
VAR
  number2 : REAL;

BEGIN   (* number2, number1 both visible here. *)
  number1 := 3.4;
  Inside1.number1 := 5.7;
  number2 := 3.9
END Inside2;

BEGIN (* Test *)
  (* only number1 visible here *)
END CheckModVisibility2.

If an item is imported or exported, then some of its component names may be implicitly imported or exported as well. Implicit import was noted earlier in connection with the identifiers of an enumeration being made available in an importing scope.

The closure of an identifier is the set consisting of that identifier, together with any all identifiers that are implicitly exported or imported whenever that item is.

The identifiers of an enumeration are part of the closure of that enumeration. However, the exports of modules are not part of the closure of that module. Thus if an entire module is imported or exported, any of its exports are not automatically exported or imported as well. However, they can be referred to in the receiving scope of the exported or imported module in qualified fashion.

NOTES: 1. This is an ISO clarification, prior to which compilers (including those from ETH) followed a variety of rules in this regard.

2. The field names of a record are available when the record type is imported to another module scope but only qualified (unless unqualified by a WITH) so they are not, strictly speaking, part of the closure of the record type, though the net result is similar.

On the other hand, exporting (importing) a procedure does not make the types of the entities in its parameter lists visible in the receiving scope; these must be done separately. So if a module contains:

PROCEDURE Swap (VAR item1, item2 : ItemType);

then importing this procedure does not make ItemType visible in the receiving scope.

If the items of the exported module appeared in an EXPORT QUALIFIED list in a module being imported or exported as a whole, then they must be qualified in the receiving scope.

If the module is exported qualified, then it must itself be referred to in qualified fashion. In the following example, modules have been indented to better indicate their scope. This is not strictly necessary for good prettyprinting.

MODULE CheckModVisibility3;

  MODULE Shell1;
  EXPORT
    Inner1;

    MODULE Inner1;
    EXPORT thing;
    VAR
      thing: CARDINAL;
    END Inner1;

  END Shell1;

  MODULE Shell2;
  EXPORT Inner2;

    MODULE Inner2;
    EXPORT QUALIFIED thing;
    VAR
      thing: CARDINAL;
    END Inner2;

  END Shell2;

  MODULE Shell3;
  EXPORT QUALIFIED Inner3;

    MODULE Inner3;
    EXPORT QUALIFIED thing;
    VAR
      thing: CARDINAL;
    END Inner3;

  END Shell3;

(* here in this outer one, Inner1 is visible, so its exports may be qualified *)
(* Inner2 is visible, so its qualified exports may be qualified *)
(* however, Inner3 itself must be qualified *)
BEGIN
  Inner1.thing := 5;
  Inner2.thing := 4;
  Shell3.Inner3.thing := 8;
END CheckModVisibility3.

As this one illustrates, the export of the identifier Inner1 from the scope determined by Shell1 into the outermost scope also causes the exports of Inner1 (namely thing) to become visible in the outermost module, but only qualified, not unqualified as some early compilers would have done.

The astute reader will probably realize by comparing previous experience with library modules that if a module's exports are QUALIFIED, then those identifiers can in turn be imported from the receiving scope (where they are qualified) into another scope by using

FROM modulename IMPORT item;

as in the following:

MODULE CheckModVisibility4; MODULE Inner1; EXPORT QUALIFIED thing; VAR thing: CARDINAL; END Inner1; MODULE Inner2; FROM Inner1 IMPORT thing; BEGIN thing:= 5; (* available here now *) END Inner2; END CheckModVisibility4.

Of course, one could not import (qualified or unqualified) the identifier thing into the outermost scope, because all imports to a scope are from the scope outside it. The only scope outside a program module is the universal scope that contains all the system and separate libraries, so all import statements in the main scope refer to the external libraries, not to the program library. An attempt to compile:

MODULE IllegalVisibility;

FROM Inner IMPORT (* illegal attempt to unqualify *)
  thing; 
  
  MODULE Inner;
  EXPORT QUALIFIED thing;
  VAR
    thing: CARDINAL;
  END Inner;
 
END IllegalVisibility.

produced the compiler error message

No symfile found for module: Inner
  --- could not open
 ---- symbolfiles missing
#    4    thing; 
#####         ^  71: identifier not exported from qualifying module
 File "RSFiles:Books:Modula book:M2.3rdEdition:TextPrograms:Cha10:IllegalVisibility.MOD"; Line 4
Modula2 - Execution terminated!

because there was no library module called Inner found in the external environment.

10.5.1 Standard Identifiers and Scope

Standard identifiers, such as REAL, CARDINAL, ABS, and so on, are also called pervasive. They should be thought of as being visible one level outside every module when that module's body is thought of as a procedure. That is, they are automatically visible at level zero of all module scopes (and any procedures inside them) without having to be imported. Another way of thinking about this is to regard pervasives as being automatically imported into every module scope. However, these identifiers differ from user-defined ones in that they may be redefined even at the outermost level if desired. Thus

MODULE RedefineStdIdent;
VAR
  CARDINAL: REAL;

END RedefineStdIdent.

is legal, but probably not useful, as it cuts off access to the identifier CARDINAL in the module. Thus if

PROCEDURE try;
VAR 
  card : CARDINAL;
END try;

were defined in the module above, there would be an error "wrong class of identifier" generated when the second CARDINAL was examined by the compiler, for it no longer is a type but a variable. However, since the rule is that standard identifiers are imported into every module scope, the following is legal, and the second use of CARDINAL inside the procedure try has its normal meaning.

MODULE RedefineStdIdent2;
VAR
  CARDINAL: REAL;
  
  MODULE Inner;
    PROCEDURE try;
    VAR 
      card : CARDINAL;
    END try;
  END Inner;

END RedefineStdIdent2.

These somewhat strange examples are presented not so much because they have many practical applications, but to emphasize the visibility rules, and that they are universally applicable, even when rather odd things result. Recall of course, that there is a fundamental difference between standard identifiers and reserved words. As the latter are not identifiers at all, they can never be re-defined or re-used in any fashion.

10.5.2 Dynamic Modules

As envisioned so far, modules and their entities are entirely compile-time phenomena, and one statement above went so far as to say that they do not even exist at run time. However, there is an exception to this in any module that is declared inside a procedure. Since the entities in a procedure scope are only realized in memory when the procedure is invoked, such a module will also come into existence only when the procedure is active. The following is legal:

MODULE RedefineStdIdent3;
VAR
  CARDINAL, card: REAL;
  
  PROCEDURE NewScope;
    MODULE Inner;
    EXPORT card;
    VAR 
      card : CARDINAL;
    END Inner;
  
  BEGIN
    card := 5;
  END NewScope;
  
END RedefineStdIdent3.

Observe that the export of the name card to the scope of NewScope is legal; it merely cuts off access to the card in the outer (procedure) scope, as one would expect. Moreover, the assignment card := 5 can be properly checked, even though the identifier CARDINAL with its built-in meaning is not visible to (and cannot be exported to) the procedure NewScope, because it does not belong to Inner and cannot be exported by it. That limitation can be gotten around of course, if the dynamic module exports its own name for the type CARDINAL as follows:

MODULE RedefineStdIdent4;
VAR
  CARDINAL, card: REAL;
  
  PROCEDURE NewScope;
    MODULE Inner;
    EXPORT Cardinal;
    TYPE
      Cardinal = CARDINAL;
    END Inner;
  
  VAR 
    card : Cardinal;
  BEGIN
    card := 5;
  END NewScope;
  
END RedefineStdIdent4.

The student should appreciate that these examples stretch the envelope of Modula-2 legalities nearly to the breaking point of common sense, but that they do so to illustrate the points being made about the scope rules rather than to inspire imitation.


Contents