home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Simtel MSDOS 1992 September
/
Simtel20_Sept92.cdr
/
msdos
/
modula2
/
mod2txt.arc
/
CHAP13.TXT
< prev
next >
Wrap
Text File
|
1987-03-25
|
24KB
|
520 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.
Load and display the program named LOCMOD1.MOD for your
first example of a program with an 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 example on your monitor at this time.
The module named "LocalStuff" 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 modified in any way
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 can be accomplished easily by
using another procedure without the module but we will see
shortly that it will not be the same.
THE BODY OF THE LOCAL MODULE
The body of the local module has one statement
contained within it, "Counter := 4;", that is executed only
when the module is loaded, and at no other time. This is
therefore an initialization section for the module. Any
Page 81
Chapter 13 - Modules, Local and Global
valid 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 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 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
never executed since it is simply a grouping identifier.
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". 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 its
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 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 it either accidentally or on purpose. In
a program as small as this one, it would not be 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 PROGRAM ON YOUR MONITOR
In this case we have one local module defined within
Page 82
Chapter 13 - Modules, Local and Global
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 its
variables are defined dynamically each time the procedure in
which it is embedded is called, and its body is also
executed each time. 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 actually nothing new except that it
is embedded in the local module "LocalStuff". 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. You therefore
have the ability to very carefully define the mechanism by
which the two modules interact.
ANOTHER LOCAL MODULE
The program we have been inspecting had the procedure
exported without 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. Load and display the program
named LOCMOD2.MOD. This program is very similar to the last
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.
Page 83
Chapter 13 - Modules, Local and Global
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.
The procedure named "WriteStuff" is even more tightly
controlled than that in the last program because this one
doesn't even 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 run this program, then 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 called in exactly the same way as these
standard modules from any program.
YOUR FIRST DEFINITION MODULE
In order to get started, load and display the program
named CIRCLES.DEF on your monitor. The first thing you will
notice is that we used a different extension for this
program because there is another part to the program with
the same name but the usual extension "MOD". What you have
displayed on your screen 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 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 just like it would do in a singly
compiled program.
The program on your monitor 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
Page 84
Chapter 13 - Modules, Local and Global
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 items are procedures. It should be
obvious that nothing within the module is available to any
other part of the program unless it is exported.
THE IMPLEMENTATION MODULE
We are not finished with the definition part of the
module yet but we will look at the implementation part of it
for a few moments. Load the program named CIRCLES.MOD and
display it on your monitor. This is the part of the module
that 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" 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 simply by importing them.
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. Anything imported into the definition part
of the module must also be imported here if it will be used
in this module, imported identifiers are not automatically
transferred into this part of the module.
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 to do so.
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
Page 85
Chapter 13 - Modules, Local and Global
you were assigned to 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. This is 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 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 relink 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
Page 86
Chapter 13 - Modules, Local and Global
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 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.
Load and display the program GARDEN.MOD for an example
of a program that calls 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. Compile and run
this program.
Page 87
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 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. All compiler writers will supply you with the
definition part of the library modules because they are your
only means of interfacing with them.
THE PROCEDURE TYPE, SOMETHING NEW
Load and display the program named PROCTYPE.MOD on your
monitor for an example of a procedure TYPE. In line 6 we
define a variable "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 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. Then in the main program the variable
"OutputStuff" is 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 type of parameter
calls as those defined when it is created as a variable.
Page 88