Pascal Programming/Routines
In the opening chapter routines were already mentioned.
Routines are, as it was described before, reusable pieces of code that can be used over and over again.
Examples of routines are read
/ readLn
and write
/ writeLn
.
You can invoke, call, these routines as many times as you want.
In this chapter you will learn
- how to define your own routines,
- the difference between a definition and declaration, and
- the difference between functions and procedures.
Different routines for different occasions
[edit | edit source]Routines come in two flavors.
In Pascal, routines can either replace statements, or they replace a (sub‑)expression.
A routine that can be used where statements are allowed is called a procedure
.
A routine that is called as part of an expression is a function
.
Functions
[edit | edit source]A function
is a routine that returns a value.
Pascal defines, among others, a function odd
.
The function odd
takes one integer
-expression as a parameter and returns false
or true
, depending on the parity of the supplied parameter (in layman terms that means whether it is divisible by 2).
Let’s see the function odd
in action:
program functionDemo(input, output);
var
x: integer;
begin
write('Enter an integer: ');
readLn(x);
if odd(x) then
begin
writeLn('Now this is an odd number.');
end
else
begin
writeLn('Boring!');
end;
end.
Odd(x)
is pronounced “odd of x”.
First, the expression in parentheses is evaluated.
Here it is simply x
, the variable’s value to be precise, but a more complex expression is allowed too, as long as it eventually evaluates to an integer
-expression.
The value of this expression, the actual parameter, is then handed to a (in this case invisible) block of code that processes the input, performs some calculations on it, and returns false
or true
according to the calculation’s findings.
The function’s returned value is ultimately filled in in place of the function call.
You can, in your mind, read false
/ true
in place of odd(x)
, although this is dynamic depending on the given input.
You are only allowed to call functions where you can put an expression. The following program is wrong:
program lostFunction;
begin
odd(42);
end.
false . But false is not a statement. You can only put statements between begin and end , no expressions.[fn 1] |
Procedures
[edit | edit source]Procedures on the other hand cannot be used as part of an expression. You can only call procedures where statements are allowed.
A routine can either be a function or a procedure. In some programming languages the routine used to retrieve data from the console can be used like a function, but this is not the case in Pascal. The following program will not compile:
program strayProcedure(input, output);
begin
if readLn(input) = '' then
begin
writeLn('Error: No input supplied.');
end;
end.
ReadLn refers to a procedure thus it does not return anything, yet at this specific position a value has to be inserted so the if ‑branch language construct and the equal comparison make sense. |
Effects
[edit | edit source]A procedure
may use functions, and the other way around.
Do not understand a function
as a mere substitute for an expression.
In the following section we will learn why.
Rationale
[edit | edit source]The dichotomy of routines, distinguishing between a procedure
and a function
, is meant to gently push the programmer to write “clean” programs.
Doing so, a routine does not conceal whether it is just a replacement for a sequence of statements or shorthand for a complex, difficult to write out expression.
This kind of notation works without introducing nasty pseudo types like, for example, void
in the C programming language where every routine is a function, but the “invalid” data type void
will allow you to make it (in part) behave like a procedure
.
Definition
[edit | edit source]Defining routines follows a pattern you are already familiar with since your very first program
.
A program
is, in some regards, like a special routine:
You can run it as many times as you want through OS-defined means.
A program
’s definition looks almost just like a routine’s.
A routine is defined by,
- a header, and
- a block
in that order.
The routine header shows a couple differences depending on whether it is a function
or procedure
.
We will first take a look at blocks, since these are the same for both types of routines.
Block
[edit | edit source]A block is the synthesis of a productive part (statements) and (optional) declarations and definitions. In Standard Pascal (as laid out by the ISO standard 7185) a block has a fixed order:[fn 2]
- constant definitions (the
const
-section) - type definitions (the
type
-section) - variable declarations (the
var
-section) - routine declarations and definitions
- sequence (
begin … end
, possibly empty)
All items but the last one, the productive part, are optional.
Sections (const , type , or var -section) may not be empty. Once you specify a section heading, you have to define/declare at least one symbol in the just started section.
|
In EP, the fixed order restriction has been lifted.
There, sections and routine declarations and definitions may occur as many times as needed and do not necessarily have to adhere to a particular order.
The consequences are detailed in the chapter “Scopes”.
For the remainder of this book we will refer to EP’s definition of block, because all major compilers support this.
Nevertheless, the order defined by Standard Pascal is a good guideline:
It makes sense to define types, before there is a section that may use those types (i. e. var
-section).
Header
[edit | edit source]A routine header consists of
- the word
function
orprocedure
, - an identifier identifying this routine,
- possibly a parameter list, and,
- lastly, in the case of functions, the data type of an expression a call to this function results in, the result data type.
The parameter list for routines also defines the data type of every single parameter.
Thus, the header of the function odd
could look like this:
function odd(x: integer): Boolean;
Take notice of the colon (:
) after the parameter list separating the function’s result data type.
You can view functions as sort of special variable declaration which also separates an identifier with a colon, except in the case of a function the “variable’s” value is computed dynamically.
Formal parameters, i. e. parameters in the context of a routine header, are separated by a semicolon. Consider the following procedure header:
procedure printAligned(x: integer; tabstop: integer);
Note that every routine header is terminated with a semicolon.
Body
[edit | edit source]While the routine header tells the processor (usually a compiler), “Hey, there’s a routine with the following properties: […]”, it is not enough. You have to “flesh out”, give the routine a body. This is done in the subsequent block.
Inside the block all parameters can be read as if they were variables.
Function result
[edit | edit source]In the sequence of the block defining a function there is automatically a variable of the function’s name. You have to assign a value exactly one time, so the function, mathematically speaking, becomes defined. Confer this example:
function getRandomNumber(): integer;
begin
// chosen by fair dice roll,
// guaranteed to be random
getRandomNumber := 4;
end;
Note that the block did not contain a var
-section declaring the variable getRandomNumber
, but it is already implicitly declared by the function’s header:
Both the name and the data type are part of the function header.
Declaration
[edit | edit source]A routine declaration happens most of the time implicitly. Declaring a routine, or in general any identifier, refers to the process of giving the processor (i. e. usually a compiler) information in order to correctly interpret your program source code. This information is not directly encoded in your executable program, but it is implicitly there. Examples are:
- A variable declaration tells the processor to install proper provisions in order to reserve some memory space. This chunk of memory will be interpreted according to its associated data type. However, neither the variable’s name, nor the data type are in any way stored in your program. Only the processor knows about this information as it is reading your source code file.
- A routine header constitutes a routine declaration (which is usually directly followed by its definition[fn 3]). Here again, the information given in a routine header are not stored directly in the executable file, but they ensure the processor (the compiler) will correctly transform your source code.
- Likewise,
type
declarations merely serve the purpose of clean and abstract programming, but those declarations do not end up in the executable program file.[fn 4]
Declarations make an identifier known to denote a certain object (“object” mathematically speaking).
Definitions on the other hand will, hence their name, define what this object exactly is.
Whether it is a value of a constant, the value of a variable, or the steps taken in a routine (the statement sequence), data defined through definitions will result in specific code in your executable file, which may vary according to the information given in related declarations;
writing a variable possessing the data type integer
is fundamentally different than writing a value of the type real
.
The code for properly storing, calculating and retrieving integer
and real
values differs, but the computer is not aware of that.
It just performs the given instructions, the circumstance that a certain set of instructions resemble operations on Pascal’s data type real
for instance is, so to speak, a “coincidence”.
Calling routines
[edit | edit source]Routing
[edit | edit source]Routines are selected based on their signature. A routine signature consists of
- the routine’s name,
- the data type’s of all arguments, and
- (implicitly) their correct order.
Thus the signature of the function odd
reads odd(integer)
.
The function named odd
accepts one integer
value as the first (and only) argument.
Overloading
[edit | edit source]Pascal allows you to declare and define routines of the same name, but differing formal parameters. This is usually called overloading. When calling a routine there must be exactly one routine of that name that accepts parameters with their corresponding data types.
Pre-defined routines
[edit | edit source]signature | description | returned value’s type |
---|---|---|
abs(integer)
|
absolute value of argument | integer
|
odd(integer)
|
parity (is given value divisible by two) | Boolean
|
sqr(integer)
|
the value squared | integer
|
Persistent variables
[edit | edit source]Some compilers, such as the FPC, allow you to use constants as if they were variables, but different lifetime.
In the following example the “constant” numberOfInvocations
exists for the entire duration of program execution, but is only accessible in the scope it was declared in.
program persistentVariableDemo(output);
{$ifDef FPC}
// allow assignments to _typed_ “constants”
{$writeableConst on}
{$endIf}
procedure foo;
const
numberOfInvocations: integer = 0;
begin
numberOfInvocations := numberOfInvocations + 1;
writeLn(numberOfInvocations);
end;
begin
foo;
foo;
foo;
end.
The program will print 1
, 2
, 3
for every call.
Lines 2, 4, and 5 contain specially crafted comments that instruct the compiler to support persistent variables.
These comments are non-standard, yet some are explained in the appendix, chapter “Preprocessor Functionality”.
Note, the concept of typed “constants” is not standardized. Some object-oriented programming extensions will give nicer tools to implement such behavior as demonstrated above. We primarily explained the concept of persistent variables to you, so you can read and understand source code by other people.
Benefit
[edit | edit source]Routines can be used as many times as you want. They are no tools of mere “text substitution”: The definition of a routine is not “copied” to the place where it is called, the call site. The size of the executable program file remains about the same.
Utilizing routines can also be and usually is beneficial to the development progress of a program. By splitting up a programming project into smaller understandable problems you can focus on solving isolated issues as part of the big task. This approach is known as divide and conquer. We now ask you to slowly shift toward thinking more about your programming tasks before you start typing anything. You may need to spend more time on thinking about, for example, how to structure a routine’s parameter list. What information, what parameters, does this routine require? Where and how can a recurring pattern be generalized through a routine definition? Identifying such questions needs time and expertise, so do not be discouraged if you are not seeing everything the task’s sample answers show. You will learn through your mistakes.
Keep in mind, though, routines are no panacea. There are situations, very specific situations, where you do not want to use routines. Recognizing those, however, is out this book’s scope. For the sake of this textbook, and in 99% of all your programming projects you want to use routines if possible. Modern compilers can even recognize some situations where a routine was “unnecessary”, yet the only gain is that your source code becomes more structured and thus readable, albeit at the expense of being more abstract and therefore complex.[fn 5]
Tasks
[edit | edit source]printM
, printI
, printS
, printP
, will significantly speed up development.program mississippi(output);
const
width = 8;
procedure printI;
begin
writeLn( '# ':width);
writeLn( '# ':width);
writeLn( '# ':width);
writeLn( '# ':width);
writeLn( '# ':width);
writeLn;
end;
procedure printM;
begin
writeLn('# #':width);
writeLn('## ##':width);
writeLn('# ## #':width);
writeLn('# #':width);
writeLn('# #':width);
writeLn;
end;
procedure printP;
begin
writeLn( '### ':width);
writeLn( '# # ':width);
writeLn( '### ':width);
writeLn( '# ':width);
writeLn( '# ':width);
writeLn;
end;
procedure printS;
begin
writeLn(' ### ':width);
writeLn(' # # ':width);
writeLn(' ## ':width);
writeLn('# # ':width);
writeLn(' ### ':width);
writeLn;
end;
begin
printM;
printI;
printS;
printS;
printI;
printS;
printS;
printI;
printP;
printP;
printI;
end.
Notes:
- ↑ Some dialects of Pascal are not so strict about that: The FPC has the option
{$extendedSyntax on}
which will allow the program above to compile anyway. - ↑ The
label
-section has intentionally been omitted. - ↑ The Extended Pascal standard allows so-called “forward declarations” [remote directive]. A forward declaration of a routine is just the declaration, no definition.
- ↑ Some compilers support the generation of non-standardized “run-time type information” (RTTI). By enabling RTTI,
type
declarations do produce data that is stored in your program. - ↑ One such compiler optimization is called inlining. This will effectively copy a routine definition to the call site. Pure functions even stand to benefit by being defined as isolated functions, provided the compiler does support appropriate optimizations.