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]

For the sake of simplicity we have been primarily using one type, value parameters, which merely pass a value to the routine. The routine gets and has its own copy of the value:

Code:

program valueParameterDemo(output);
	var
		x: integer;
	procedure foo(x: integer);
		begin
			x := 7185;
			writeLn(x);
		end;
	begin
		x := 10206;
		foo(x);
		writeLn(x);
	end.

Output:

       7185
      10206
Although 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]

Warning Due to their nature, routine parameters always have to be (a special kind of) value parameters.

Variable parameters

When any changes to the parameter should propagate outside, to the call site, Pascal provides us with variable parameters. They are indicated simply by prepending the word var in the formal parameter list:

Code:

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.

Output:

       7185
       7185
This subtle change has the effect that 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.

Warning 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 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:

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

parameter kinds available in Pascal[fn 3]
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 variables), 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 n1, 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 variables. 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

Change 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.
All you need to do is swap the sixth line with the seventh line. Now you print n first before moving on to printing n1 and so on.
All you need to do is swap the sixth line with the seventh line. Now you print n first before moving on to printing n1 and so on.


Write a recursive 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.
It can be abbreviated even further, but an acceptable solution could look like this:
program recursionPractice(output);
	type
		integerNonNegative = 0..maxInt;
Remember to restrict the domain, that is the range of values the 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.
Note, to obtain number of digits of a positive integer you can use the mathematical expression .
It can be abbreviated even further, but an acceptable solution could look like this:
program recursionPractice(output);
	type
		integerNonNegative = 0..maxInt;
Remember to restrict the domain, that is the range of values the 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.
Note, to obtain number of digits of a positive integer you can use the mathematical expression .


Write a 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
As we do not know the line’s length in advance a text file is the ideal data type to buffer each input line.
			line: text;
Since Pascal does not enforce any constraints on variable parameters (except that variables need to be supplied), ensure you unequivocally document whether and which parameters need to be initialized. For 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;
By the way, 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 tac(input, output);
	procedure lineSwap;
		var
As we do not know the line’s length in advance a text file is the ideal data type to buffer each input line.
			line: text;
Since Pascal does not enforce any constraints on variable parameters (except that variables need to be supplied), ensure you unequivocally document whether and which parameters need to be initialized. For 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;
By the way, 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.


Write a 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.
An acceptable recursive implementation may look like this:
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.
An acceptable recursive implementation may look like this:
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:

  1. For the possibility of remote directives, including the directive forward, see a later chapter of this book.
  2. Extended Pascal modules and UCSD Pascal units do provide such a facility to some extent.
  3. 1 2 Extended Pascal adds another distinction called protected which prohibits overwriting a parameter.
  4. 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 or record holding many members). If a parameter is only read, a mere pointer could be sufficient. An optimizing compiler can perform such an optimization.
  5. 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).
  6. Note that this circumstance is an implementation detail. The proper Pascal term is variable parameter.
  7. While it may seem obvious to the human reader that n needs to be initialized before calling collatzStep, 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.
  8. Delphi provides another parameter kind: out. It is a variable parameter, but only for writing. Reading from an out parameter will emit a warning during compilation. This kind of parameter is also supported by the FPC if {$modeSwitch out+}.
  9. In general routine A calling routine B calling routine A again is also called recursion. The intervening call of B is harmless.
  10. In practice (except for so‐called tail recursion), delving deeper into recursion requires more computer memory, but eventually reserving more memory fails.
  11. 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.

Next Page: Schemata|Previous Page: Files
Home: Pascal Programming
Category:Book:Pascal Programming#Scopes%20
Category:Book:Pascal Programming