CSE 428: Lecture Notes 8


Procedures and Functions

Procedures and functions are a way of enriching a language with new commands and operations, respectively. Any high-level modern programming language offers the possibility of defining them. Advantages include:

Syntax

Declaration

Procedure/function declarations consist of A function should contain in the statement block also an instruction specifying the value to be returned.

Note: in C, C++ and Java both procedures and functions are called "functions". Procedures are characterized by a "void" return type. The "kind" is implicit.

Examples

Call

Procedure/function calls consist of

Examples

Parameter passing methods

In the above examples the parameters were always passed by value. However, programming languages allow (usually) also other methods. The most used are:

Call by value

Call by value is probably the most used parameter passing method. Almost all high-level languages support call by value, with the exception of Logic Programming Languages, like Prolog.. Some languages (like C and Java) only allow call by value.

In call by value, the actual parameter can be a general expression (it does not need to be a variable).

Meaning

At the moment of the call, a location l is created locally for the formal parameter (so formal parameters are like local variables). The actual parameter is evaluated and the resulting value is stored in l. Then the body is executed.

Call by reference

Call by reference is used in those cases in which we want the procedure/function change the value of the arguments. It is supported by several imperative languages, including C++ and Pascal.

In call by reference, the actual parameter should be a variable.

Meaning

At the moment of the call, the location l of the actual parameter is associated to the formal parameter (so the formal parameter becomes an alias of the actual parameter). Then the body is executed.

Example

In C++ reference parameters are indicated by the symbol "&". The following example shows a possible definition of a procedure "swap" which exchanges the values of the parameters:
   void swap(int &x, int &y){
      int temp;
      temp = x;
      x = y;
      y = temp;
   }
Example of a call to swap:
   int n = 1;
   int k = 2;
   swap(n,k);
   cout << n; // prints 2
   cout << k; // prints 1
In languages which do not have call by reference, like C, we can simulate it by using pointers called by value. Here is an example of swap implemented in C:
   void swap(int *x, int *y){
      int temp;
      temp = *x;
      *x = *y;
      *y = temp;
   }
Of course, in this case the call to swap must pass the location addresses of the variables to be swapped:
   int n = 1;
   int k = 2;
   swap(&n,&k);
   cout << n; // prints 2
   cout << k; // prints 1

Notes: "call by reference" and "call by value using pointers" are similar, but are not the same thing. In particular, when we want to simulate call by reference by using pointers called by value, we must be careful to use the actual parameters always in a dereferenced mode.

Call by reference is actually implemented in C++ by using pointers and automatic deferencing.

C++ allows actual parameters to be generic expressions, but in such case the formal parameters must be constants. When the actual parameter is not a variable, then a temporary variable is created at the moment of the call, the actual parameter is evaluated and its value is stored in the location of the temporal variable. The lifetime of the temporal variable is the activation time of the procedure.

Call by result and call by value-result

Also call by result and call by value-result are used to change the value of the arguments. They were introduced in some languages as an alternative to call by reference to avoid the unclarities related to aliases.

Also in these cases the actual parameters should be variables.

Meaning

At the moment of the call, a location l is created locally for the formal parameter (like in call by value). In call by value-result, the value of the formal parameter (i.e. the value in the location l' associated to the actual parameter) is copied at this point in l. (This step is not present in call by result.) Then the body is executed. When the procedure returns, the final value of l (which usually will be different from the initial one) is copied back in l'.

Example

Consider the following declaration of a procedure p with two value-result parameters (we use a C++like notation, but please note that C++ does not support call by value result).
   void p(int value-result x, int value-result y){
      x = x + 1;
      y = y + 1;
   }
The following is an example of a call to p:
   int z = 0;
   p(z,z);
   cout << z; // prints 1
Note that if the parameters were passed by reference, then the same fragment of code would have printed the value 2. This is because in the call p(z,z), x and y (and z) would be aliases for the same location.

Call by name and call by need

Call by name was used by some imperative languages like Algol W, and it is used by several functional languages, like Haskell. Its primary use is the implementation of non-strict (aka short-circuit) operators.

A strict operator always causes the evaluation of its parameters. Conversely, a non-strict operator may or may not evaluate them, depending on the situation. An example of non-strict operator is the "logical or" of C++ (||), which evaluates the second argument only if the first is false. The logical or of Pascal, on the contrary, is strict.

Example

Compare the following instructions in C++ and Pascal:
   if (x==0 || 1/x > 0.3) ...  // C++
   
   if (x=0 or 1/x > 0.3) ...   // Pascal
If x = 0, the Pascal instruction will give error (floating exception: division by 0) because 1/x > 0 is evaluated despite the first expression is true. The C++ instruction, on the contrary, will not give error: the evaluation of the condition stops after the evaluation of the first argument and returns true.

Meaning of call by name

In call by name the formal parameter is just a place-holder for the actual parameter. At the moment of the call, the formal parameter is associated to the code of the expression representing the actual parameter. (This treatment of the parameter is similar to macro-expansion, but some care must be taken for the non-locals in case of stic scope.) In particular, note that there is no evaluation of the actual parameter at the moment of the call. The actual parameter will be evaluated only during the execution of the body, if needed.

Meaning of call by need

Call by need is similar to call by name; the only difference is that in call by need the actual parameter is evaluated only the first time it is needed. Then the value is stored and used whenever it is needed again. In call by name, on the contrary, the parameter is re-evaluated each time is needed. These different disciplines may bring to different result in imperative languages, whenever non-local variables occurring in the actual parameter change value between two evaluations.

Note that in both call by need and call by name the actual parameter may be generic expressions (it's the all point, in fact).

Example

Implementation of a non-strict logical or (we use a C++like notation, but please note that C++ does not support call by name).
   bool or(bool name a, bool name b){
      if (a) 
         return true;
      else
         return b;
   }
Example of a call:
   if (or(x==0 , 1/x > 0.3)) ... // when x = 0 the condition evaluates to true

Note: Call by value cannot be used to implement non-strict operators, because the parameters are evaluated at the moment of the call (even if not needed). Call by reference or call by value-result cannot be used either, because the actual parameters (in general) must be variables. Note that even in case the language allow to use generic expressions in call by reference (like C++), they are evaluated first and stored in temporary variables. Hence the non-strictness effect cannot be achieved.

Example

Consider the following definition in C++
   bool or(const bool &a, const bool &b){
      if (a) 
         return true;
      else
         return b;
   }
Example of a call:
   if (or(x==0 , 1/x > 0.3)) ... // when x = 0 the condition gives error