Functions and procedures
Naming a piece of work: a function hands a value back through its own name, a procedure does not, and a var parameter lets it change the caller.
Declared above the block that calls them
A routine is written between the declarations and the begin of the program. Pascal reads top to bottom, so a routine has to appear before the code that calls it; a call above the definition is an unknown name.
The header says everything: function Square(x: integer): integer; names the routine, lists its parameters with their types, and gives the type of the value it returns. A procedure header has no return type because it returns nothing.
Parameters are written name first, then a colon and the type, and several of the same type share one colon: procedure Show(text: string; n: integer);.
A function returns through its own name
There is no return keyword in this dialect. A function assigns its result to its own name, which is why the body contains an assignment rather than a statement that hands the value back. That assignment can happen anywhere in the body and more than once, with the last one winning.
Calling a function is an expression, so it can sit inside writeln, inside arithmetic or on the right of an assignment. Calling a procedure is a statement of its own.
Because the name is a result slot, it behaves like a local variable: it can be read, added to, and assigned more than once. That freedom is a trap as much as a convenience. A branch that assigns the result and a branch that forgets leaves the caller with whatever the slot happened to hold, and the compiler may only warn.
var parameters change the caller
By default a parameter is a copy. Changing it inside the routine changes the copy, and the caller never notices — which is usually what you want.
Prefix a parameter with var and the routine is given the variable itself instead of a copy. Now an assignment inside the routine changes the caller, which is how a procedure hands back more than one result.
That is a real design choice rather than a formality: a var parameter says the routine will write to your variable, and a reader can see it in the header without reading the body.
A var parameter must be given something assignable: a variable, a field, or an array slot. A literal or an expression has no address to write back to, so DoubleIt(5) is rejected. That restriction is the compiler protecting you from a call whose result would have nowhere to go.
One of each
program Main;
var
value: integer;
function Twice(n: integer): integer;
begin
Twice := n * 2;
end;
procedure Bump(var n: integer);
begin
n := n + 1;
end;
begin
value := 5;
writeln(Twice(value));
Bump(value);
writeln(value);
end.What a call puts on the stack
A call does more than jump to another line. The machine pushes a return address, makes room for the parameters and the routine's local variables, and gives the body a fresh area to work in. That area is the stack frame, and it is why two calls to the same function cannot see each other's locals.
A value parameter copies its argument into the frame; a var parameter stores the address of the caller's variable, so an assignment writes back through it. A function also has a slot for its result, which is why assigning to the function name works like assigning to a local. Recursion is the same mechanism repeated: each call gets its own frame, and the deepest call returns first.
A worked call, step by step
Follow writeln(Square(n)) with n holding 5. The argument is evaluated first, so the value 5 is copied into the parameter slot. Control jumps to Square, whose body multiplies its own x by itself and assigns 25 to the result slot. The routine returns, the frame disappears, and the caller receives 25 as the value of the call. Then writeln prints it.
With DoubleIt(n), the same steps happen, but the parameter slot holds the address of n rather than a copy of 5. The body writes 10 through that address, so the caller sees 10 afterwards. Remove the var and the address becomes a copy; the body still computes 10, and the caller still has 5.
Three routine mistakes to watch for: a function that never assigns its name, which leaves the result undefined even though the program compiles; a var parameter given a literal or an expression, which has no variable to write back to; and a routine placed below its caller, where the name is still unknown.
Try these next
- Add a function
CubebesideSquareand call it twice with different arguments. - Pass the same variable to
DoubleIttwice and predict the value after each call. - Give a function a local variable with the same name as a program variable and print both.
A function and a var procedure
Run it, then remove the var from the procedure header and run again. Look at what the middle line prints, then put the var back.
Remove the var from DoubleIt and run again: the number printed afterwards goes back to five, because the procedure then works on a copy.
A factorial function with a loop
Run it, then add a call for 6 and predict the answer before running. Then change `ShowFactorial` so it prints the number before the result on the same line.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
How does a function hand back its result?
