Pascal Programming/Scopes
So far for didactic reasons all programs and concepts were kept as concrete as possible. In this chapter you will get to know one very abstract notion which you have already been using all the time, although were not (made) aware of.
Blocks
In the opening chapter you were briefly acquainted to the term block. Blocks were defined in the chapter “Routines”. Let’s revisit the meaning of block:
Term
A block is the combination of optional declarations plus a possibly empty sequence of statements. For instance
var
x: integer;
begin
x := 42;
end
is one block.
The begin
and end
is mandatory, whereas everything else is optional.
Note the absence of a program
or routine header.
When a block follows such a header, it defines the program
or routine in question.
For instance, the line
procedure foo;
merely declares “there is a procedure
going by the name foo
”.
This declaration needs to be followed by a block[fn 1] to define foo
.
To separate the definition of a routine from the following code, a block must be concluded with a semicolon (;
); a program
on the other hand concludes with a dot (.
).
In Pascal this last character is indeed not part of a block.
Scope
This should not be news to you, but there has not been a technical explanation.
All declarations made within a block are only valid until the respective end
.
This principle is known as block scope.
It becomes particularly apparent with routines:
program scopeDemo(output);
const
answer = 42;
procedure foo;
const
answer = 1337;
begin
{ Here, `answer` refers to `1337`. }
writeLn(answer);
end;
begin
{ Here, however, `answer` refers to `42`. }
writeLn(answer);
foo;
end.
Without defining another value for answer
inside the defining block of foo
, the value of answer
would be 42
.
However, the redefinition “shadows” the outside constant.
Concededly, re‑using the identifier answer
is a poor choice and therefore usually avoided, yet sometimes this is desired and used deliberately.
The term scope was first mentioned when you were introduced to the with
‐clause.
Note, unlike a record
you cannot “select” a particular scope.
It is not possible to write scopeDemo.answer
to refer to the “outside” constant.[fn 2]
Value parameters
The rules of scope also apply to routine parameters. You can access parameters only within the associated block (and possibly any nested blocks). The name of a parameter cannot be redeclared in the immediately following block; this would constitute a duplicate identifier.
Pascal supports essentially knows two kinds of parameters: value parameters and variable parameters.[fn 3]
program valueParameterDemo(output);
var
x: integer;
procedure foo(x: integer);
begin
x := 7185;
writeLn(x);
end;
begin
x := 10206;
foo(x);
writeLn(x);
end.
7185
10206
foo(x)
is called before the writeLn(x)
in line 12, the assignment x := 7185
has no effect “outside” of foo
. Two different values are printed.Value parameters can be deemed a (light‐weight) variant of variables.
Within the associated block (as well as any nestede blocks) you can read from and write to the parameter.
However, it is not possible to use them as counter variables in a for
‑statement.[fn 4]
![]() |
Due to their nature, routine parameters always have to be (a special kind of) value parameters. |
Variable parameters
var
in the formal parameter list:
program variableParameterDemo(output);
var
x: integer;
procedure foo(var x: integer);
begin
x := 7185;
writeLn(x);
end;
begin
x := 10206;
foo(x);
writeLn(x);
end.
7185
7185
7185
is printed twice. The procedure foo
does not have its “own copy”, but acts on x
in the scope of where it was called from.[fn 5]Because variable parameters are frequently implemented by using pointers, and pointers are a kind of reference, variable parameters are commonly dubbed “pass by reference” parameters.[fn 6]
In the following code, the implementation of alterIntegerViaPointer
shows what your processor possibly does under the hood when implementing alterIntegerVariable
.
program variableParameterAsReferences(output);
type
integerReference = ^integer;
var
x: integer;
integerLocation: integerReference;
procedure alterIntegerVariable(var x: integer);
begin
x := x * 2;
end;
procedure alterIntegerViaPointer(xLocation: integerReference);
begin
xLocation^ := xLocation^ * 2;
end;
begin
x := 8;
alterIntegerVariable(x);
writeLn(x);
new(integerLocation);
integerLocation^ := 8;
alterIntegerViaPointer(integerLocation);
writeLn(integerLocation^);
end.
Using variable parameters is the recommended and obviously the most convenient method. Note, pointers are not the only type of references.
![]() |
File variables always have to be passed as var parameters. This is because, as mentioned in the chapter on files, file variables merely provide us with an abstract handle, a reference, to interact with a file managed by the OS. Therefore, just using the handle, it is impossible to make local modifications that do not show any side effects outside the scope (i. e. the behavior value parameters should have by definition). |
When calling a routine that takes variable parameters, it is mandatory to specify variables as actual parameters, not expressions. Specifying an expression would defeat the purpose of a variable parameter, i. e. being able to modify the value at the call site.
Choice
Variable parameters require additional self‐discipline from the programmer.
![]() | The following piece of code is buggy. The procedure collatzStep presumes n was referring to an initialized variable. It is reading from n in odd(n) and before making an assignment. However, at the call site x is still uninitialized. Therefore collatzStep receives a reference to an uninitialized variable. This is bad.
program faultyInitializedPresumption(output);
var
x: integer;
procedure collatzStep(var n: integer);
begin
if odd(n) then
begin
n := 3 * n + 1;
end
else
begin
n := n div 2;
end;
end;
begin
collatzStep(x);
writeLn(x);
end.
|
The compiler cannot emit some kind of error[fn 7] because routines accepting variable parameters may legitimately take uninitialized variables. It is therefore not adequate to generally throw an error “dude, you forgot to initialize this variable”.
Remedy: When you are adding variable parameters to your toolbox you will need to show additional discipline. Document your routines’ parameters, whether they need to be initialized or if it is optional.[fn 8] Last but not least, prefer value parameters:
![]() |
The more code you write (even if it is just inserting the word var ), the more complexity it means. |
Parameter overview
Choose the parameter type wisely. You need a reason why you opt for one kind or another. Let’s summarize the options available to you:
formal parameter | actual parameter |
---|---|
A text or file of … variable, as well as any variable containing such (e. g. a record ) must be passed as a var parameter:procedure doSomething(var F: text);
begin
end;
|
At the call site you will need to specify the name of a variable matching the data type.var
F: text;
begin
doSomething(F);
end
|
A routine parameter is specified by providing a routine header. Only the signature matters. The names of any parameters are irrelevant. In this example the parameter x could be named rumpelstilzchen without any difference.
program routineParameterSummary(output);
function yIntercept(
function f(x: integer): integer
): integer;
begin
yIntercept := f(0);
end;
|
When calling yIntercept you will need to specify the name of a routine matching the data types.function line(x: integer): integer;
begin
line := -8 * x + 42;
end;
begin
writeLn(yIntercept(line));
end.
|
A variable parameter requires the word var in front of the formal parameter’s specification:program variableParameterSummary(output);
procedure saturatingIncrement(var i: integer);
begin
i := i + ord(i < maxInt);
end;
|
A variable parameter necessitates, hence its name, a variable.var
x: integer;
begin
x := maxInt;
saturatingIncrement(x);
end.
|
Value parameters do not require any special indication. | You provide a value to the routine by specifying an expression. This expression can be made up of constants and/or variables as long as it eventually evaluates to the necessary data type. This is subject to automatic promotion: You may specify an integer value even though the routine expects a real value. |
Recursion
In the chapter on pointers we learned that pointer data types can be declared before the “pointed data type” was even declared. Routines have kind of a similar property.
Step
A routine invoking itself is called recursion.[fn 9] Each time a routine calls itself we take a recursion step. How does this look like? Well, it is as simple as that:
program recursionDemo(output);
procedure writeNaturalNumbers(n: integer);
begin
if n > 0 then
begin
writeNaturalNumbers(n - 1);
writeLn(n);
end
end;
begin
writeNaturalNumbers(3)
end.
Notice that the writeNaturalNumbers(n - 1)
appears within the block defining the writeNaturalNumbers
procedure
.
The definition, the block, has not concluded yet (we have not reached the end;
) thus the definition of writeNaturalNumbers
is incomplete.
Nevertheless we can call the routine we are currently defining.
This works because routine activations (calling/invoking routines) do not require knowledge of the implementation, the definition (the associated block).
All you need to know to successfully activate (call) a routine is its name, the total number of parameters and their data types.
All of that information is given in routine declaration (the procedure
or function
line immediately preceding a block).
This is can be compared to pointer data types not requiring any knowledge of the referenced data type.
Base case
When you take a recursion step, something should and in fact must change. The decision whether to take another recursion step must depend on “something.” If nothing changed the recursion would (in theory) go on forever.[fn 10]
The most frequent method is to use different actual parameter values: the recursion step involves calling the same routine with a different value than it itself (in this recursion step) was called. However, the routine’s definition prevents taking another recursion step depending on an expression that involves the actual parameter’s value.
You can, if you like, in your mind copy the routine definition of a recursive routine that does not employ additional declarations (i. e. no extra var
iables), to the call site, but need to replace all occurences of the actual parameter reference with the altered expression.
What does that mean?
Consider the following recursive procedure
.
procedure writeUnaryNumber(n: integer);
begin
if n > 0 then
begin
write('I');
writeUnaryNumber(n - 1);
end;
end;
Now, everywhere writeUnaryNumber
is called you replace it by what is between (the routine definition’s) begin
and end
.
procedure writeUnaryNumber(n: integer);
begin
if n > 0 then
begin
write('I');
begin
if n > 0 then
begin
write('I');
writeUnaryNumber(n - 1);
end;
end;
end;
end;
However, you need to replace n
with n−1
, that is the expression writeUnaryNumber
in the recursive case gets called with.
Parentheses are necessary so it is guaranteed this does not alter the meaning (e. g. unintended consequences because of operator precedence).
begin
if (n - 1) > 0 then
begin
write('I');
writeUnaryNumber((n - 1) - 1);
end;
end;
You can do the entire process as many times as you want.
procedure writeUnaryNumber(n: integer);
begin
if n > 0 then
begin
write('I');
begin
if (n - 1) > 0 then
begin
write('I');
begin
if ((n - 1) - 1) > 0 then
begin
write('I');
writeUnaryNumber(((n - 1) - 1) - 1);
end;
end;
end;
end;
end;
end;
This method may be useful in an exam:
the examiner asks what a recursive algorithm does but there are no meaningful identifiers.
But, again, it works only if there are no additional var
iables.
Obviously, here writeUnaryNumber
prints as many I
’s as n
if it is non‐negative;
the procedure
name already says that.
Performance
Mathematicians find recursive definitions particularly elegant. For example, most students are first acquainted with the factorial function (indicated by appending a ) by its recursive definition:
As programmers we are less concerned about esthetics than we are about performance. In particular, recursive routines are in general quite inefficient since they introduce a certain computing overhead. This circumstance of performance is not rooted in programming language Pascal but due to the way recursive routines are implemented. Therefore, programmers use the iterative definition of the factorial function
which is a concise way of writing
function factorial(n: integer): integer;
var
i, result: integer;
begin
result := 1;
for i := 1 to n do
begin
result := i * result
end;
factorial := result
end;
It is, however, wrong to avoid recursive routines at all costs.
For example, exponentiation of a real
number to a non‐negative integer
exponent has a straightforward iterative definition and implementation:
Yet repetitive multiplication requires multiplication operations in total, which is terribly slow for any large . In fact we can significantly speed up calculations by employing a recursive definition
because this requires only multiplications, i. e. fewer than multiplications if .[fn 11] This is not a math textbook, so do not worry if the reasoning is not apparent.
Tasks
writeNaturalNumbers
in above recursionDemo
so it produces a reverse list of natural numbers, the largest number is printed first, the smallest natural number 1
last.n
first before moving on to printing n−1
and so on.function
that tells you how many digits you need to write a non‐negative number to the base of ten. Hint: The algorithm involves repeated division by a certain divisor.program recursionPractice(output);
type
integerNonNegative = 0..maxInt;
function
accepts, as appropriate. You do not want your function
to return 1
(the number of required decimal digits) for a given value of, for example, −999
. That would be wrong. function decimalDigitCount(n: integerNonNegative): integer;
begin
{ n < 10 is the base case. }
if n < 10 then
begin
decimalDigitCount := 1
end
else
begin
decimalDigitCount := 1 + decimalDigitCount(n div 10);
end
end;
begin
writeLn('Writing ', 123:1, ' to base ten requires ',
decimalDigitCount(123):1, ' decimal digits.')
end.
program
that prints the first input line last and the last input line first. You may not use any array
or pointer variables though. Since it is a topic of this chapter, it is fairly obvious that this task can be solved with recursion.program tac(input, output);
procedure lineSwap;
var
text
file is the ideal data type to buffer each input line. line: text;
copyLine
both text
variables need to be initialized and in a certain state: { `source` must be open for reading, `destination` open for writing. }
procedure copyLine(var source, destination: text);
begin
while not EOLn(source) do
begin
destination^ := source^;
put(destination);
get(source)
end;
{ Final `get` actually reads and discards the EOL character. }
get(source);
{ EOL character cannot be copied; file buffer contains space. }
writeLn(destination)
end;
copyLine
is a nested procedure
because it is specialized to the use in lineSwap
. If it were more robust for general use, e. g. checked EOF
as appropriate, it might have just as well been placed outside of lineSwap
. In general: Nest specialized routines as deep as reasonable. begin
if not EOF(input) then
begin
rewrite(line);
copyLine(input, line);
lineSwap;
reset(line);
copyLine(line, output)
end
end;
begin
lineSwap
end.
program
that prints every input line in reverse. The first character of an input line is printed last, and the last input character is printed first (the end‐of‐line‐character is exempt from reversal). You may not use any array
or pointer variables though. Since it is a topic of this chapter, it is fairly obvious that this task can be solved with recursion.program reverse(input, output);
procedure reverseLine;
var
buffer: char;
begin
if not EOLn then
begin
read(buffer);
reverseLine;
write(buffer)
end
else
begin
readLn;
writeLn
end
end;
begin
while not EOF do
begin
reverseLine
end
end.
Notes:
- ↑ For the possibility of remote directives, including the directive
forward
, see a later chapter of this book. - ↑ Extended Pascal modules and UCSD Pascal units do provide such a facility to some extent.
- 1 2 Extended Pascal adds another distinction called
protected
which prohibits overwriting a parameter. - ↑ Although value parameters behave like as if the routine had its own copy, it is not necessary to implement it this way. Copying data to create an independent copy is an expensive operation (well, for “big” variables like an
array
orrecord
holding many members). If a parameter is only read, a mere pointer could be sufficient. An optimizing compiler can perform such an optimization. - ↑ In some implementations a variable parameter is implemented just like a value parameter (i. e. “own copy”), but the processor inserts invisible code that writes back the modified value(s) to the call site (the location where the routine is called).
- ↑ Note that this circumstance is an implementation detail. The proper Pascal term is variable parameter.
- ↑ While it may seem obvious to the human reader that
n
needs to be initialized before callingcollatzStep
, it is not that simple when programming a compiler. Detecting this circumstance would essentially require some kind of “simulated execution”, an effort compiler vendors usually avoid. - ↑ Delphi provides another parameter kind:
out
. It is a variable parameter, but only for writing. Reading from anout
parameter will emit a warning during compilation. This kind of parameter is also supported by the FPC if{$modeSwitch out+}
. - ↑ In general routine
A
calling routineB
calling routineA
again is also called recursion. The intervening call ofB
is harmless. - ↑ In practice (except for so‐called tail recursion), delving deeper into recursion requires more computer memory, but eventually reserving more memory fails.
- ↑ The division operation is associated with abysmal performance. However, if the computer uses binary numbers, the division by two (or in fact any positive integral power of two) can be implemented by a trivial bitshift operation, so the division operation is negligible.