Pascal Programming/Units

From Wikibooks, open books for an open world
Jump to navigation Jump to search

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]

A UCSD Pascal unit is like a program except that it cannot run on its own, but is supposed to be used by programs. 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.

Note 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]

A unit definition shows many similarities to a regular program, but with many additional features.

Header[edit]

The first line of a unit looks like this:

1 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]

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:

  1. the interface part, and
  2. 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:

1 unit myGreatUnit;
2 interface
3 implementation
4 end.

The interface part has to come before the implementation part. Also note that units 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]

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]

Import[edit]

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.

Note 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]

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]

Dependencies[edit]

More features[edit]

Initialization and Finalization section[edit]

Distribution without source code[edit]

Unit design[edit]

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]

Run-time system[edit]

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.

Note Since these units provide the standard routines of Pascal they have to be imported as the very first unit. However, due to human’s proclivity to err the compiler will take care of ensuring the RTS is loaded first. Therefore, it is forbidden to write uses system;. The FPC will emit an error if you are attempting to import the RTL manually.

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]

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]

The Extended Pascal standard lays out a specification for modules. These provide advanced means of modularization. However, neither FPC nor Delphi support this, only the GPC does.

Tasks[edit]

Write a unit that says goodbye on program termination, i. e. prints a message to the terminal.
You can utilize the finalization section as hook to achieve that behavior:
unit friendly;
interface
implementation
finalization
begin
	writeLn('Goodbye!');
end;
end.
You can utilize the finalization section as hook to achieve that behavior:
unit friendly;
interface
implementation
finalization
begin
	writeLn('Goodbye!');
end;
end.

Notes:

  1. The module concept described in Extended Pascal standard does allow module parameterization.
  2. As of version 3.0.4 the FPC does not support propagation of identifiers.
  3. PXSC-style modules require the usage of use global someModuleName in order to enable propagation of identifiers.

Next Page: Object oriented | Previous Page: Scopes

Home: Pascal Programming