Pascal Programming/Routines

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

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.

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.

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,

  1. a header, and
  2. 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]

  1. constant definitions (the const-section)
  2. type definitions (the type-section)
  3. variable declarations (the var-section)
  4. routine declarations and definitions
  5. sequence (begin end, possibly empty)

All items but the last one, the productive part, are optional.

Note 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

  1. the word function or procedure,
  2. an identifier identifying this routine,
  3. possibly a parameter list, and,
  4. 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

  1. the routine’s name,
  2. the data type’s of all arguments, and
  3. (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.

Note In some other programming languages the data type of the returned value also belongs to a routine’s signature. Remember differing definitions of the term signature should you ever switch between programming languages.

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]

Pascal’s pre-defined functions (excerpt)
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]

Write a (now infamous) program that writes the word “Mississippi” in large (spanning at least three lines) capital letters. It should become apparent that writing four routines, printM, printI, printS, printP, will significantly speed up development.
An acceptable answer could look like this
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.
An acceptable answer could look like this
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:

  1. 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.
  2. The label-section has intentionally been omitted.
  3. The Extended Pascal standard allows so-called “forward declarations” [remote directive]. A forward declaration of a routine is just the declaration, no definition.
  4. 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.
  5. 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.


Next Page: Enumerations | Previous Page: Expressions and Branches
Home: Pascal Programming