Chapter 13 MODULES, LOCAL AND GLOBAL PREREQUISITES FOR THIS MATERIAL ______________________________________________________________ Before attempting to understand this material, you should have a good grasp of the principles taught in Part I of this tutorial. None of the material from Part II is required to do a meaningful study of modules in Modula-2. WHAT GOOD ARE MODULES? ______________________________________________________________ Modules are the most important feature of Modula-2 over its predecessor Pascal, making it very important for you to understand what they are and how they work. Fortunately for you, there are not too many things to learn about them and after you master them, you will find many uses for them as you develop programs, and especially large programs. Keep in mind, as you study this material, that modules are meant to organize a program to make it understandable to the original programmer, and to the person assigned to do the maintenance on the program. Examine the program named LOCMOD1.MOD for =============== your first example of a program with an LOCMOD1.MOD embedded module. Modules are nothing new =============== to you because every program you have examined has been a module. At this time, however, we will introduce a local module. WHAT IS A LOCAL MODULE? ______________________________________________________________ A local module is simply a module nested within another module, just like the present example. The module named LocalStuff in lines 8 through 21 is nested within the main module and is heavily indented for clarity. Since nothing is imported into the local module, nothing that belongs to the main module can be seen from within the nested module. In addition, since the procedure GetNumber is the only thing exported from the local module, nothing else is available to the main module. In effect, the local module is an impenetrable wall through which nothing can pass without the benefit of the import and export list. In this case, the variable Counter cannot be read or modified by the main module, either intentionally or accidentally and the procedure GetNumber will very stubbornly refuse to allow any flexibility in its output, adding three to its internally stored variable each time it is called. It may seem to you that this result 13-1 Chapter 13 - Modules, Local and Global can be accomplished easily by using another procedure without the module but we will see shortly that it will not be the same. Throughout the following discussion of modules, keep in mind that a module is an impenetrable wall through which nothing can pass without use of an import or export list. THE BODY OF THE LOCAL MODULE ______________________________________________________________ The body of the local module has one statement contained within it in line 20, "Counter := 4;", which is executed only when the module is loaded, and at no other time. This is therefore an initialization section for the module. Any valid executable statements can be put here and they will be executed when the program is loaded, or you can omit the body altogether by omitting the begin and any executable statements. Actually, this body is no different than the body of the main program since it too is executed one time when the program is loaded, except for the fact that the main program is required to have a body or you will have no program. The body can have a return statement included in it. If the return statement is executed, the remainder of the body will not be executed, but will be ignored. THE MODULE VERSUS THE PROCEDURE ______________________________________________________________ We must digress a bit to see the difference in these two important topics in Modula-2. A procedure is an executable section of code whereas a module is a grouping of variables, constants, types, and procedures. A module is not executed since it is simply a grouping identifier, except of course for the main module which is defined as the main program. The variables in a procedure do not exist when it is not being executed, but instead are generated dynamically when the procedure is called. A variable therefore, has a lifetime associated with it in addition to a type and a scope of visibility. This may seem strange to you, but if you think about it for awhile, it will help explain how recursive procedure calls work. The module, on the other hand, exists anytime it's surrounding code exists, in this case, the main program. Since the module always exists, the variable Counter also always exists because it is defined as a part of the module. If this variable were defined within a procedure, it would be automatically regenerated every time the procedure were called it and would therefore not remember the value it contained the prior time the procedure was called. We could choose to define the variable as global and it would therefore always be available and never regenerated, but we would be left with the possibility of anything in the program modifying 13-2 Chapter 13 - Modules, Local and Global it either accidentally or on purpose. The ability to isolate a variable and other objects from the rest of the program is called information hiding, and has become a highly visible topic in software engineering in recent years. In a program as small as this one, accidental corruption of data is not a problem, but it is intended to illustrate the solution to a problem embedded in a much larger program. Suppose, for example, that you wished to generate random numbers for some use within a program. You could include all of the code within a module using the module body for the seed initialization, and a procedure to generate one random number each time it was called. The structure would be essentially the same as that given here, but the actual code would be different. Nothing in the main program or any of its procedures could in any way corrupt the job given to the random number generator. BACK TO THE EXAMPLE PROGRAM ______________________________________________________________ In this case we have one local module defined within the main module but as many as desired could be used, and we have one procedure in the local module whereas we could have as many as desired. In fact, we could have local modules embedded in a procedure, or in other local modules. There is no real limit as to how you can structure your program to achieve the desired results. One thing must be remembered. If you embed a local module within a procedure, all of it's variables are defined dynamically each time the procedure in which it is embedded is called, and the local module's body is also executed each time the procedure is called. This can be used to advantage in some situations, but it would be best to leave this construct to the future when you have more experience with Modula-2. In the body of the main module you will find nothing new except for the call to the function procedure GetNumber() which is really nothing new to you. Since GetNumber() is exported from the local module, it is available for use in the main program. Compile and run the program to see if it does what you expect it to do. TWO LOCAL MODULES ______________________________________________________________ It would be well to point out at this time that if you define two local modules at the same level, one could export a variable, procedure, constant, or type and the other could import it and use it in any legal fashion. If a record or enumeration type is exported, all of the associated identifiers are exported also, and are available anyplace the 13-3 Chapter 13 - Modules, Local and Global type is imported. You therefore have the ability to very carefully define the mechanism by which the two modules interact. It is even possible to export procedures of the same name from more than one module, but at least one is required to use a qualified export to prevent a name clash. More will be said about this later. Of course, as you probably realize by now, anything exported from either of the two local modules are available for use by the main program without importing them. ANOTHER LOCAL MODULE ______________________________________________________________ The program we have been inspecting had =============== the procedure exported without LOCMOD2.MOD qualification, so it could only be =============== referred to by its simple name. This could have led to a naming conflict which can be solved by using a qualified export as is done in the next program. Examine the program named LOCMOD2.MOD. This program is very similar to the last one except for moving the output statements to the procedure. First, you should notice that the procedure name is exported using EXPORT QUALIFIED which allows the use of the qualified call to the procedure in line number 25. There can never be a conflict of names in calling a procedure this way because it is illegal to use the same name for a module more than once at any level. In a local module, you have a choice of using either qualified or unqualified export of items, but the exported items must all be of the same export type because only one export list is allowed per module. IMPORTING INTO A LOCAL MODULE ______________________________________________________________ The three output procedures are used in the local module MyStuff, but because it is only permissible to import items from a module's immediate surroundings, the procedures must first be imported into the main module. Remember that a module is actually an impenetrable wall which nothing can get through without benefit of being imported or exported. The display output procedures are imported into the main module in line 4, but they are not automatically imported into MyStuff. Line 9 is required to import them into the local module. They had to be imported through two barriers in this case. The procedure named WriteStuff is even more tightly controlled than that in the last program because this one doesn't even 13-4 Chapter 13 - Modules, Local and Global return a value to the calling program. It updates its own internally stored value, displays it, and returns control to the calling program. Compile and execute this program, and when you are sure you understand the new concepts covered here, we will go on to global modules. GLOBAL MODULES ______________________________________________________________ As useful as local modules are, they must take a back seat to the global module with which you are already fairly familiar because you have been using them throughout this tutorial. The modules InOut, Terminal, and FileSystem are examples of global modules that you already know how to use. Now you will learn how to write your own global modules that can be imported and used in any program in exactly the same way as these standard modules. A global module is not executable, it is simply a collection of related types, variables, and procedures. It is meant to group items in a meaningful way. YOUR FIRST DEFINITION MODULE ______________________________________________________________ In order to get started, examine the =============== program named CIRCLES.DEF at this time. CIRCLES.DEF The first thing you will notice is that we =============== used a different extension for this file because there is another part to the program with the same name and the usual extension MOD. This is the definition part of the global module and it serves two very important purposes. First, it defines the interface you need to use the module in one of your programs, and secondly, it defines the details of the interface for the compiler so it can do type checking for you when you call this module. The Modula-2 compiler uses the information contained here to check all types and numbers of variables during separate compilation, just like it would do in a program compiled as a complete unit. This program actually does very little. In fact, its purpose is to do nothing because there are no executable statements in it. It is only to define the interface to the actual program statements contained elsewhere. Notice that the procedures are exported using the qualified option. All identifiers that are exported from a definition module must be qualified so that the user has the option of importing them either way. It is legal to export procedures, variables, constants, or types for use elsewhere as needed for the programming problem at hand, but the majority of exported 13-5 Chapter 13 - Modules, Local and Global items will probably be procedures. It should be obvious that nothing within the module is available to any other part of the program unless it is exported. The export list is not actually required because according to the Modula-2 definition, all identifiers in a definition module are automatically exported. Most experienced programmers include the export list for explicit documentation. WHAT ABOUT QUALIFIED EXPORT? ______________________________________________________________ If the identifiers are exported using the qualified export, the user has a choice of importing using the qualified method or the unqualified method. If the entire module is imported using the qualified form; IMPORT Stuff; then the various entities must be referred to using the qualident form such as Stuff.ProcedureName. If the module is imported using the unqualified form; FROM Stuff IMPORT ProcedureName, ...; then the various entities have the qualifier removed, and the simple procedure names can be used to refer to the procedures and variables. Once again, we need to discuss a new construct in Modula-2. The newest and preferred method of importing a module using the qualified form is given as; FROM Stuff IMPORT; This is identical to the qualified form given above because it imports all entities and the programmer is required to use the qualident form in the program, but it greatly simplifies the compiler writers job. It will eventually be required, but at this point in time, few compilers will require it so the alternate form will be used in this edition of the Modula-2 tutorial. THE IMPLEMENTATION MODULE ______________________________________________________________ We are not finished with the definition =============== part of the module yet but we will look at CIRCLES.MOD the implementation part of it for a few =============== moments. Examine the program named CIRCLES.MOD at this time. This is the part of the module that 13-6 Chapter 13 - Modules, Local and Global actually does the work. Notice that there are three procedures here, two of which were defined in the definition part of the module making them available to other programs. The procedure named GetPi, in lines 4 through 7, is a hidden or private procedure that is only available for use within this module. The other two procedures are available to any program that wishes to use them provided it imports them first, just as we have been doing with other procedures in this tutorial. They are therefore referred to as visible procedures. Anything defined in the definition part of the module is also available here for use without redefining it, except for the procedure headers, which must be completely defined in both places. Note that export is not allowed from an implementation module since the definition module defines the external interface and anything in the implementation module is hidden. MORE ABOUT THE USE OF TWO PARTS ______________________________________________________________ The definition part of the module defines the public information about the module and the implementation part of the module defines the private or hidden information about the module. It may seem sort of silly to go to the trouble of separating a module into two parts but there are at least three good reasons for performing this separation. 1. You may not care how the module is implemented. In all of the programs we have run up to this point, you probably didn't care how the WriteString procedure did its job. You only wanted it to do the job it was supposed to do to aid you in learning to use Modula-2 efficiently. It would have been senseless to have cluttered your monitor with the details of how it worked every time you wanted to know how to use it. 2. It hides details of implementation. If you were working on a large programming project and you were assigned the job of writing a procedure for others to use that did some well defined task, you would define the interface carefully and be finished. If, however, one of the users studied your detailed code and found a way to trick it into doing something special, he may use the trick in his part of the program. If you then wanted to improve your routine and remove the code that allowed the trick, the interface would no longer work. To prevent this, you give others only the interface to work with and they cannot look for tricks. As mentioned earlier, this is 13-7 Chapter 13 - Modules, Local and Global called information hiding and is a very important technique which is used on large projects. 3. It allows for orderly development. It is possible to define all of the definition parts of the modules and have all members of the development team agree to the interface. Long before the details of the individual procedures are worked out, the entire team knows what each procedure will do and they can all begin detailed work on their respective parts of the overall system. This is very effective when used on a large team effort. COMPILATION ORDER IS IMPORTANT ______________________________________________________________ In order for the above principles to work effectively, a very definite order of compilation must be adhered to. If the identifiers declared in the definition part are automatically available in the implementation part of the module, then it is obvious that the definition part must be compiled before the implementation part of the module can be compiled. Also, if the definition part is modified and recompiled, then the implementation part may also require modifications to comply with the changes and it must also be recompiled. The next rule is not nearly so obvious but you will understand it when we explain it. When a calling module is compiled, it checks each of the imported identifiers to see that the types and number of variables agree with the calling sequences used in the program. This is part of the strong type checking done for you by Modula-2. If you modify and recompile one of the called definition modules and attempt to link the program together, you may have introduced a type incompatibility. In order to prevent this, Modula-2 requires you to recompile every module that calls a modified definition module. It does this by generating a key when you compile a definition module and storing the key when you compile the calling module. If you attempt to link a program with differing keys, this indicates that the definition module was changed, resulting in a new key and hence a mismatch, and the linker will generate an error. WHY ALL OF THIS TROUBLE? ______________________________________________________________ It may not seem to be worth all of the extra trouble that the Modula-2 compiler and linker go through to do this checking, but it is important for a large program. The information used in the definition part of the module is the type of information that should be well defined in the design stages of a programming project, and if well done, very few or no changes should be required during the coding phase of the 13-8 Chapter 13 - Modules, Local and Global project. Therefore it is expected that recompiling several definition modules should not happen very often. On the other hand, during the coding and debugging phase of the project, it is expected that many changes will be required in the implementation parts of the modules. Modula-2 allows this and still maintains very strong type checking across module boundaries to aid in detecting sometimes very subtle coding errors. The above paragraph should be interpreted as a warning to you. If you find that you are constantly recompiling modules due to changes in the definition modules, you should have spent more time in the software design. NOW TO ACTUALLY USE IT ALL ______________________________________________________________ With all of that in mind, it will be necessary for you to reload the program named CIRCLES.DEF which is the definition part of the module, and compile it. Your compiler will generate several different files for use in cross checking. After you get a good compile, reload the program named CIRCLES.MOD which is the implementation part of the module and compile it. During this compile, some of the files generated by CIRCLES.DEF will be referred to. It would be an interesting exercise to modify a procedure call in one of the programs to see what kind of an error is displayed. After a good compile on both of these modules, you have a new module in your library that can be used just like any of the other global libraries that came with your compiler. This module cannot be executed because it does not contain a main program, it is only a collection of useful procedures. Examine the program GARDEN.MOD for an ================ example of a program that calls and uses GARDEN.MOD your new library or global module. This ================ program is very simple and should pose no problem in understanding for you. The two new procedures are imported and used just like any other procedure. Be sure to compile and run this program. Examine the program named GARDEN2.MOD for =============== an example of the use of unqualified GARDEN2.MOD import. In this case, we input all =============== exported procedures and must use the qualifier for each call as illustrated in lines 12 and 13. The program executes identically to the last program, but be sure you compile and execute it to verify that it really is identical. 13-9 Chapter 13 - Modules, Local and Global A FINAL WORD ABOUT GLOBAL MODULES ______________________________________________________________ From the above description of global modules, it may not be very obvious to you that it is perfectly legal for one global module to call another which in turn calls another, etc. Program structure is entirely up to you. For example, we could have called WriteString and some of our other familiar procedures from within the AreaOfCircle procedure. The order of compilation must be kept in mind , however, or you will not get a good compilation and linking of your completed program. Remember that there is nothing magic about the global or library (the names are synonymous) modules supplied with your compiler. They are simply global modules that have already been programmed and debugged for you by the compiler writer. This is probably a good time to mention to you that you may have only received the source code for the definition part of the library modules with your compiler. Many compiler writers will supply the source code for the implementation part of the library modules only if you supply them with a little more money. After all, they are in business for the money and most people never wish to modify the supplied routines but are happy to use them as is. Compiler writers must supply you with the definition part of the library modules because they are your only means of interfacing with them. THE OPAQUE TYPE ______________________________________________________________ The opaque type is illustrated in the ================ example program named OPAQUETY.DEF. OPAQUETY.DEF Examination of line 6 will reveal that the ================ actual type is not defined, only its name. In this case the structure of the type will be hidden from the user of this module in order to effect a measure of information hiding, which we mentioned earlier. You will notice that the opaque type (so called because it is hidden from view) is used in each of the procedure headers. The type is used once in each header, but it could be used more times if needed, or not used at all if the problem dictated it. The next example program named ================ OPAQUETY.MOD gives the implementation of OPAQUETY.MOD the complete module and as you can see, ================ the type is completely defined here as a pointer to a record. Since the definition is in the implementation module, it is effectively hidden from view to the outside world. You should have no problem understanding the details of the implementation. The example program named OPTYPE.MOD makes use of the opaque type exported from the OpaqueType module. There are a few rules which must be followed here. Since the structure of the 13-10 Chapter 13 - Modules, Local and Global opaque type is not exported, the user ================ cannot work with individual elements, but OPTYPE.MOD can only use the procedures made available ================ to him by the writer of the module. The only operations which the user is allowed to do on variables of the opaque type are assignment as illustrated in lines 12 through 14, and comparison for equality or inequality as illustrated in line 29. Only the simple types and pointers are allowed to be used as opaque types, according to the original definition of Modula- 2. The newest additions to the language allow the use of records and arrays also, but since most compilers do not have this feature added yet, they are not illustrated here. The preceeding three files could be easily changed to export an opaque type of a record if your compiler supports it. THE PROCEDURE TYPE, SOMETHING NEW ______________________________________________________________ Examine the program named PROCTYPE.MOD for ================ an example of a procedure type. In line PROCTYPE.MOD 6, we define a variable named OutputStuff ================ to be a procedure type of variable that requires an ARRAY OF CHAR as an argument. This variable name can now be used to refer to and call any procedure that uses a single ARRAY OF CHAR as an argument. In the definition part of the program two procedures are defined, each of which uses a single ARRAY OF CHAR as an argument. In the main program, the variable OutputStuff is successively assigned each of the new procedures and used to call them. In addition, it is used to call the supplied procedure WriteString to illustrate the possibility of doing so. Finally, the procedures are all called in their normal manner to illustrate that there is nothing magic about them. Any procedure type can be used to call any procedures that use the same number and types of parameters as those defined when it is created as a variable. This is a relatively new operation since it is not available in the other popular programming languages. It is also a construct that you will not use very much, if ever, so nothing more will be said about it in this tutorial. 13-11