As a follow-up to the discussion of the last section, it is important to note that while the name of a procedure itself is part of the scope surrounding it, the parameters of a procedure are part of the procedure's own scope. The types of those parameters, however, must be available in the surrounding scope. These rules have several effects:
1. The name of the procedure is available for use in its own scope because of the inheritance rule. This makes recursion possible.
2. The name of a procedure may be redefined inside the procedure, because it belongs to the outer scope.
3. The names of parameters cannot be re-used as variables within the procedure, because they do belong to its scope.
4. The parameters' types cannot be defined in the procedure itself; these types must be resolved in the surrounding scope.
That is to say, the following is legal:
PROCEDURE Scope (VAR theNum : REAL); VAR Scope : BOOLEAN; BEGIN (* statement sequence *) END Scope;
and the scope diagram for a procedure, drawn to include this information about the parameters, would look like figure 10.5.
It is probably not very useful to do such a redeclaration; the information is included here only to emphasize the location of the scope boundary.
On the other hand, the following two are both illegal:
PROCEDURE BadScopeA (VAR card : CARDINAL); VAR card : CARDINAL; (* can't do this *) BEGIN (* statement sequence *) END BadScopeA; PROCEDURE BadScopeB (VAR theThingy : ThingyType); TYPE ThingyType = ARRAY [0..7] OF BOOLEAN; (* can't do this either *) BEGIN (* statement sequence *) END BadScopeB;
Now consider another example of the potential for harm done by a procedure having side effects, this time caused by the inappropriate use of variable parameters. Sometimes, such parameters are used when one does not really intend to generate a side effect, and a value parameter would have been more appropriate. To illustrate, consider the following pathological example wherein the action of a variable parameter is misused to produce unexpected side effects:
MODULE ValVarDemo; FROM STextIO IMPORT WriteLn; FROM SWholeIO IMPORT WriteCard; PROCEDURE ValAdd (card1, card2: CARDINAL; VAR result: CARDINAL); BEGIN card2 := card1 + 1; result := card1 + card2 END ValAdd; PROCEDURE VarAdd (VAR card1, card2: CARDINAL; VAR result: CARDINAL); BEGIN card2 := card1 + 1; result := card1 + card2 END VarAdd; (* notice that the only difference between the two is the fact that the two value parameters in the first are declared as variable parameters in the second. *) VAR (* main program material declared here *) mainNum, answer: CARDINAL; BEGIN (* first, do it with value parameters *) mainNum := 10; ValAdd (mainNum, mainNum, answer); WriteCard (answer, 3); WriteLn; (* now, repeat with variable parameters *) mainNum := 10; VarAdd (mainNum, mainNum, answer); WriteCard (answer, 3); WriteLn; END ValVarDemo.
When this program is compiled and run, the output is:
The reason that the two answers are different is that when the second procedure is active, all three of card1, card2, and mainNum are names for the same memory location. (Recall that the action of a variable parameter is to alias the actual name to the same location named by the formal name.) Thus, when the first line in the procedure VarAdd changes the memory location named by card2, it does so for all three names (see figure 10.4), and the next line will add two copies of the value 11, rather than a 10 and an 11 as did the first procedure.
While this may seem to be an extreme example, it does show how the different nature of the two kinds of parameters affects the meaning of programs. Errors of this kind in real-life programs are likely to be much more subtle and difficult to find. This discussion also points to the rule of thumb:
Never use a variable parameter when a value parameter will do.
Note especially that function procedures are employed expressly to return a value to an expression; they can have no side effects at all--provided they have no variable parameters. Indeed, they should not, for there is no way to abandon the evaluation of the expression in which the function appears in order to check that side effect. For instance, if one has the syntactically legal.
PROCEDURE DoIt (a : REAL; VAR success: BOOLEAN) : REAL;
it is impossible to check on the (presumed) error result during a call like
x := DoIt (23.5, ok);
Likewise, the expression
a := Funct (x, y) + y;
if y is a variable parameter, may result in a change to y before the second instance of y in the expression is evaluated. This is unlikely to produce the expected result, and certainly makes the meaning of this line of code hard to understand. (Its meaning depends not on its own form, but on the inclusion of the variable parameter in the definition, and the logic of the execution, and this is not what is expected of functions.) Thus, the rules of thumb:
Never use a variable parameter in a function procedure.
Use function procedures in preference to regular procedures with variable parameters.