Pascal Programming/Units
In original Standard Pascal all functionality of a program other than the standard functions Pascal already defines had to be defined in one file, the program
source code file.
While in the context of teaching the sources remained rather short, entire applications quickly become cluttered despite various comments structuring the text.
Quite soon different attempts to modularize programs emerged. The most notable implementation that remains in use till today is UCSD Pascal’s concept of units.
UCSD Pascal units
[edit | edit source]A UCSD Pascal unit is like a program
except that it cannot run on its own, but is supposed to be used by program
s.
A unit
can define constants, types, variables and routines just like any program
, but there is no executable portion that can be run independently.
Using a unit means that this unit becomes a part of the program;
It is like copying the entire source code from the unit
to the program
, but not quite the same.
Usually, units are stored in separate files thus incredibly cleaning up the program
’s source code file.
However, this is not a set requirement, since after an end.
a module is considered complete and another module may follow.
From now on, as another layer of abstraction, module refers to either a program or unit . (In FP a library is another type of module.)
|
Defining units
[edit | edit source]A unit
definition shows many similarities to a regular program
, but with many additional features.
Header
[edit | edit source]The first line of a unit
looks like this:
unit myGreatUnit;
Unlike a program
there is no parameter list.
A unit
is a self-contained unit of certain functionality, thus cannot be parameterized in any way.[fn 1]
This line also declares a new identifier, in this example myGreatUnit
.
MyGreatUnit
becomes the first component of the so-called fully-qualified identifier.
More on that later.
Parts
[edit | edit source]The unit
concept provides means to encapsulate its definitions, so that the programmer using the unit
does not need to know how certain functionality is implemented.
This is done by splitting the unit
into two parts:
- the
interface
part, and - the
implementation
part.
A programmer using another unit only needs to know how to use the unit:
This is outlined in the interface
part.
The programmer who is programming the unit on the other hand will need to implement the unit’s functionality in the implementation
part.
Thus a bare minimum unit looks like this:
unit myGreatUnit;
interface
implementation
end.
The interface
part has to come before the implementation
part.
Also note that unit
s terminate with a end.
just as a program
does.
The interface
part of unit
consists of a block, except that it cannot contain any statements.
The interface
is merely declaratory.
All identifiers defined in the interface
part will become “public”, i. e. a programmer using the unit will have access to them.
All identifiers defined in the implementation
part, on the other hand, are “private”:
they are only available within the unit’s own implementation
part.
There is no way to circumvent this separation of exported and “private” code.
Example
[edit | edit source]unit randomness;
// public - - - - - - - - - - - - - - - - - - - - - - - - -
interface
// a list of procedure/function signatures makes
// them usable from outside of the unit
function getRandomNumber(): integer;
// a definition (an implementation) of a routine
// must not be in the interface-part
// private - - - - - - - - - - - - - - - - - - - - - - - - -
implementation
function getRandomNumber(): integer;
begin
// chosen by fair dice roll
// guaranteed to be random
getRandomNumber := 4;
end;
end.
Using units
[edit | edit source]Import
[edit | edit source]Now, it is great that we have finally outsourced some code, but the point of all of this is to use the outsourced code.
For this, UCSD Pascal defines the uses
clause.
A uses
clause instructs the compiler to import another unit’s code and familiarize with all identifiers declared in the interface
part of that unit.
Thus, all identifiers from the unit’s interface
part become available, as if they were part of the module importing them via the uses
clause.
Here is an example:
program chooseNextCandidate(input, output);
uses
// imports a unit
randomness;
begin
writeLn('next candidate: no. ', getRandomNumber());
end.
Note, that the program
chooseNextCandidate
neither defines nor declares the function getRandomNumber
, but nevertheless uses it.
Since getRandomNumber
’s signature is listed in the interface
part of randomness
, it is available for other modules using that module.
Each program may have at most one uses clause. It has to appear right after the program header.
|
Uses
clauses are allowed in any module.
Of course it is possible to use other units inside a unit
.
Moreover, you are allowed to have two uses
clauses in one unit
, one in the interface
and one in implementation
part each.
The units listed in the interface
part’s uses
clause propagate, that means they become also to the module that uses such units.[fn 2][fn 3]
Namespaces
[edit | edit source]Now, programming with units would have been a hassle if all units that were ever programmed had to explicitly define exclusive identifiers.
But this is not the case.
With the advent of modules all modules implicitly constitute a namespace.
A namespace is a self-contained scope where only within identifiers need to be unique.
You are quite welcome to define your own getRandomNumber
and still use the randomness
unit.
In order to distinguish between identifiers coming from various namespaces, identifiers can be qualified by prepending the namespace name to the identifier, separated by a dot.
Thus, randomness.getRandomName
unambiguously identifies the getRandomNumber
function exported by the randomness
unit.
This notation is called fully-qualified identifier, or FQI for short.
Precedence
[edit | edit source]This section is empty. Please help by expanding it. |
Dependencies
[edit | edit source]This section is empty. Please help by expanding it. |
More features
[edit | edit source]Initialization and Finalization section
[edit | edit source]This section is empty. Please help by expanding it. |
Distribution without source code
[edit | edit source]This section is empty. Please help by expanding it. |
Unit design
[edit | edit source]There are several considerations that should be accounted for:
- Whenever some code might be useful for other programs too, you may want to create a separate unit.
- One unit should provide all functionality necessary in order to be useful, however,
- a unit should not provide features that are unrelated to its main purpose.
- Your unit’s usability largely depends on well-defined interface. Requiring knowledge of the specific implementation is usually an indicator for bad code.
Special units
[edit | edit source]Run-time system
[edit | edit source]Some compilers use units for providing certain functionality that serves the gray zone between a compiler’s actual task and a program
(i. e. what you write).
Most notably, Delphi, the FPC as well as the GPC provide a run-time system (RTS) that includes all standard routines defined as part of the language (e. g. writeLn
and ord
).
In Delphi and the FPC this unit is called system
, whereas the GPC comes with the GPC
unit.
These units are sometimes referred to as run-time library, RTL for short.
Knowing what the RTS’s unit is called could be useful, since this implies that all identifiers of the RTL are part of one namespace.
That means, in (for example) Delphi and the FP one may refer to the standard function abs
by both, its short name as well as the FQI system.abs
.
The latter may be required if you are shadowing the abs
function in the current scope, but need to use Pascal’s own abs
function.
Debugging
[edit | edit source]The FPC comes with a special unit heapTrc
(heap trace).
This unit provides a memory manager.
It is used to find out whether the program
does not release any memory blocks it earlier reserved for itself.
Allocating memory and not handing it back to the OS is called “memory leaking” and is a very bad circumstance.
Due to the heapTrc
unit’s intrusive behavior into Pascal’s memory management, it also needs to be loaded very soon after the system
unit has been loaded.
Hence, FPC forbids you to explicitly include the heapTrc
unit in the uses
clause, but provides the -gh
compiler command-line switch that will ensure inclusion of that unit.
The heapTrc
unit is only used at the development stage.
It can print a memory report after the program
’s final end.
.
The heapTrc
unit is somewhat easy to use, but also limited in its features.
We recommend to use dedicated debugging and profiling tools such as valgrind(1)
as knowing how to use such tools will serve you well if you ever switch programming languages.
If you specify the -gv
switch on fpc(1)
’s invocation, the FPC will insert debugging information for usage with valgrind(1)
.
Other modularization implementations
[edit | edit source]The Extended Pascal standard lays out a specification for module
s.
These provide advanced means of modularization.
However, neither FPC nor Delphi support this, only the GPC does.
Tasks
[edit | edit source]finalization
section as hook to achieve that behavior:
unit friendly;
interface
implementation
finalization
begin
writeLn('Goodbye!');
end;
end.
Notes: