Having seen how to define generic separate modules, it is time to turn attention to the production of specific instances of these library modules by providing the actual parameters that will determine a specific use. This is done by employing another new kind of module called a refining module.
A refining separate module consists of a refining definition module and a refining implementation module. The refining definition module provides to a generic definition module that it names the actual parameters to substitute for its formal parameters so that a definition module may be refined. The refining implementation module provides to a generic implementation module that it names the actual parameters to substitute for its formal parameters so that an implementation module may be refined.
The syntax of a refining separate module is very simple. It consists of the word DEFINITION or IMPLEMENTATION (as the case may be) followed by the word MODULE followed by the name to be given to the resulting (refined) module, then an equal sign, and then the formal parameter list. To be consistent with the rest of Modula-2, it concludes with an END and a repetition of the name of the module.
Generic definition modules are refined separately from their implementation parts because the designer may wish to test the interface in a client program before writing any of the code, even in generic fashion.
A refining definition module is similar to a definition module in the sense of the base language, and the rules for definition modules given in the base standard apply, with two additions or changes.
First, the imports of a refining definition module are the imports of the definition module of the generic separate module of which it is a refiner together with the results of evaluating the actual module parameters (i.e. these values may be treated as imports.)
Second, the exports of a refining definition module are the refinements of the items defined in the definition module of the generic separate module of which it is a refiner.
A refining definition module cannot have additional imports or declarations of its own in addition to the ones in the generic separate module from which it refines.
The effect of compiling a refining definition module is the same as constructing and then compiling a definition module (in the sense of the base language) obtained from the definition module of the generic separate module that it refines from but with the results of evaluating the actual parameters of the refining definition module substituted for the formal parameters of the definition module of the generic separate module that is being refined. The resulting refinement that is translated is a definition module in the sense of the base language, and the translation that is done following refinement employs only the rules of the base language.
Because this refinement is performed by supplying actual parameters, the parameters of the refining definition module must match the parameters of the generic definition module of which it is a refiner. If the definition module of the generic separate module has no parameters, the refining definition module shall also have none, not even an empty parameter list. The syntax of a refining definition module is shown in figure 16.3.
Examples
Example 1: What follows is a possible refiner of the first example in section 16.2.1
DEFINITION MODULE CardStack = Stacks (CARDINAL); END CardStack.
Example 2: In order to refine the module Sorts as a separate module, one must first supply the necessary Compare procedure in a separate module. For instance to refine an integer sort, first supply:
DEFINITION MODULE IntegerInfo; FROM Comparisons IMPORT CompareResults; PROCEDURE Compare (a, b : INTEGER) : CompareResults; END IntegerInfo.
When this is done, refining is by a module such as:
DEFINITION MODULE IntSorts = Sorts (INTEGER, IntegerInfo.Compare); END IntSorts.
Example 3: Any number of specific instances of the generic counter may be refined, though this may not be a common use of the facility:
One refiner that illustrates the syntax is:
DEFINITION MODULE ACount = Counter; END ACount.
Example 4: The generic matrix is refined with constants, as in the following:
DEFINITION MODULE RealMatrix45 = Matrix (4, 5, REAL); END RealMatrix45.
At some point before linking a final client program together, the implementation parts of any generic definition modules that have been refined for the project must also be refined from the corresponding generic implementation modules. This is done using exactly the same syntax as for refining definition modules, except of course with the word IMPLEMENTATION.
A refining implementation module is similar to an implementation module in the sense of the base standard, and the rules for implementation modules (and their relationships with the corresponding definition modules) given in the base standard apply, with the following addition.
The imports of a refining implementation module are the imports of the implementation module of the generic separate module of which it is a refiner together with the actual parameters of the refining implementation module (i.e. the values may be treated as imports.)
The effect of compiling a refining implementation module is the same as constructing and then translating an implementation module (in the sense of the base standard) obtained from the implementation module of the generic separate module that it refines from, but with the results of evaluating the actual parameters of the refining module substituted for the formal parameters of the generic separate module that is being refined. The resulting refinement that is translated is an implementation module in the sense of the base language, and the translation that is done following refinement employs only the rules of the base language. The syntax of a refining implementation module is shown in figure 16.4.
NOTE: The parameters of the refining implementation module must be the same as the parameters of the corresponding refining definition module.
Example 1: What follows is a possible refining module of the first example in section 16.2.1
IMPLEMENTATION MODULE CardStack = Stacks (CARDINAL); END CardStack.
Example 2: To complete the refiner of the generic sorting module in 16.2.1, one still needs:
IMPLEMENTATION MODULE IntSorts = Sorts (INTEGER, IntegerInfo.Compare); END IntSorts.
A client has:
FROM IntSorts IMPORT Quick;
or, if more than one refinement to separate modules is done:
IMPORT IntSorts, RealSorts, RecSorts;
Example 3: Here is a refining implementation part of the counter example:
IMPLEMENTATION MODULE ACount = Counter; END ACount.
Example 4: To illustrate the necessity of the parameters for both parts of the module being the same, here is the refining implementation for the corresponding refining definition module of example 4 in section 16.3.1
IMPLEMENTATION MODULE RealMatrix45 = Matrix (4, 5, REAL); END RealMatrix45.
Any number of separate refinements of a generic separate module can be performed under a variety of names. Thus the generic module Matrix could be refined to hold reals, cardinals, integers, or any other numeric data type, and the module ACounter could be refined several times, each with a different name to produce different counters. However, two precautions have to be observed:
1. Different refinements should be done by refiners with different names, so that if two are imported into a single client there will not be a name clash.
2. In a module like Matrix there will be many operations on the matrices that use arithmetic operations on the individual elements. For instance, two matrices of the same size are added by adding the elements on corresponding positions. Such operations require that the elements actually have addition defined on them. Thus, while the compiler might allow
DEFINITTION MODULE BoolMatrix45 = Matrix (4, 5, BOOLEAN); END BoolMatrix45.
because the parameters are correct, the attempt to use the refiner
IMPLEMENTATION MODULE BoolMatrix45 = Matrix (4, 5, BOOLEAN); END BoolMatrix45.
would fail, not because of the parameters per se but because when it then attempted to compile the result after making the parameter substitutions, there would be type incompatibility errors in the arithmetic operations. Similar problems could arise in other contexts as well, so the programmer must ensure that refinements are appropriate to the data types used and the operations employed on them. That is, not all refinements of a given module are logically correct, even if at the parameter substitution level, they may appear to be syntactically correct.
These comments highlight the need to plan ahead of time to take such situations into account. That is why the module Sorts was planned from the start to take a Compare procedure as a parameter. Had this module simply relied on the less than operator for comparisons, only numeric data could have been sorted by it.
If the generic separate module has formal parameters, any refiner of it must provide corresponding actual parameters. These are evaluated, and the resulting arguments are accessed in the refined module through the identifiers of the formal parameters. As when using procedure parameters, it is important to ensure that actual module parameters are compatible with the formal ones in the generic modules from which refining is being done.
First, if the formal parameters are constant value parameters, the corresponding actual parameters must be constant expressions of a type compatible to the formal parameter type.
NOTE: This means that actual parameters may not be variables. This differs from the corresponding situation with procedure value parameters, which are allowed to be variables. The reason for this restriction is that the compiler must be able to evaluate the actual parameters and construct the refined module from them, and a compiler cannot evaluate variables because they do not get their values until run time.
Second, for a formal TYPE parameter the identifier of a visible type shall be provided. Any type that is visible at the point of the refining module is potentially usable as an actual parameter, or as the type of an actual parameter. This includes pervasive types, user defined types, or types qualified by a module name.
NOTE : Since a module parameter list may include items of types named previously in the list, such lists shall be evaluated left to right‹a restriction not present in the base language for evaluating parameters of procedures.
Other than the restrictions on refining module actual parameters (to constant expression parameters and type parameters), and the requirement of left-to-right evaluation, the rules for these parameters (matching, compatibility) are the same as those given in the base standard for actual procedure parameters of these kinds. This means, for instance, that the parameters must be visible at the point in the module where they are being used. One may use a separate library module item by naming it as an identifier qualified by the module name in the manner of StextIO.WriteLn because the library module names are all visible to the compiler. The effect is similar to an import.