home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Education Master 1994 (4th Edition)
/
EDUCATIONS_MASTER_4TH_EDITION.bin
/
files
/
progmisc
/
modultut
/
chap13.txt
< prev
next >
Wrap
Text File
|
1989-01-18
|
29KB
|
647 lines
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