CSE 341 -- Autumn 2005 -- Parameter Passing

The following techniques are used to pass arguments in traditional imperative languages: Call by value and call by name are also important concepts for functional languages. Other techniques:

call by value: copy going into the procedure

call by result: copy going out of the procedure

call by value result: copy going in, and again going out

call by reference: pass a pointer to the actual parameter, and indirect through the pointer. If the actual parameter is a variable or an array element (not an expression), then the procedure can assign to the formal parameter and as a result assign into the actual parameter as well. (The more general programming language term for "variable or array element" or the like is l-value, in other words, something that can be on the left hand side of an assignment statement.)

call by name: re-evaluate the actual parameter on every use. For actual parameters that are simple variables, this is the same as call by reference. For actual parameters that are expressions, the expression is re-evaluated on each access. It should be a runtime error to assign into a formal parameter passed by name, if the actual parameter is an expression. One possible implementation: use anonymous function ("thunk") for call by name expressions. (This is traditionally used to implement call by name in languages such as Algol and Simula.)

Call by name is not supported in recent languages, but is still an important concept in programming language theory. (In newer languages, if you want the effect of call by name, pass a procedure as a parameter.)

For a pure functional language, lazy evaluation has exactly the same semantics as call by name, but will generally be more efficient. With lazy evaluation, the actual parameter is not evaluated until the formal parameter's value is needed. At that time, the actual parameter is evaluated, and the value is cached. Then, if the value of the formal parameter is needed again, the cached value will be used. (This is safe in a pure functional language, since the value of an expression will always be the same each time it is evaluated.) Thus, with lazy evaluation, an actual parameter is evaluated either 0 or 1 times.

Call by value is particularly efficient for small pieces of data (such integers), since they are trivial to copy, and since access to the formal parameter can be done efficiently. Java uses call by value for primitive data types.

Call by reference is particularly efficient for large pieces of data (such as large arrays), since they don't need to be copied.

Fortran uses call by reference
Insecurity in early FORTRANs -- passing a constant allowed the procedure to change the constant!

Algol 60 has call by name, call by value

Ada uses different designations: IN, OUT, IN OUT:

For objects (not primitive types), Java uses call-by-value with pointer semantics. In other words, Java passes a copy of the reference to the object to the called method (but doesn't copy the object itself). Smalltalk and Scheme work in exactly the same way. Unless you assign into the formal parameter, this gives the same answers as call-by-reference. However, if you assign into the formal parameter, the link with the actual parameter is broken. (Actually, Smalltalk won't let you assign into the formal parameter, but Java and Scheme will.)

An important related concept: aliasing. Two variables are aliased if they refer to the same storage location. A common way that aliasing can arise is using call by reference. A formal parameter can be aliased to a nonlocal variable, or two formal parameters can be aliased.


Example of call by value versus value-result versus reference

The following examples are in an Algol-like language.
    begin
    integer n;
    procedure p(k: integer);
        begin
        n := n+1;
        k := k+4;
        print(n);
        end;
    n := 0;
    p(n);
    print(n);
    end;
Note that when using call by reference, n and k are aliased.

Output:

call by value:        1 1
call by value-result: 1 4
call by reference:    5 5


Example of call by value versus call by name

    begin
    integer n;
    procedure p(k: integer);
        begin
        print(k);
        n := n+10;
        print(k);
        n := n+5;
        print(k);
        end;
    n := 0;
    p(n+1);
    end;
Output:
call by value:     1  1  1
call by name:      1 11 16


Example of call by reference versus call by name

This example illustrates assigning into a parameter that is passed by reference or by name

    begin
    array a[1..10] of integer;
    integer n;
    procedure p(b: integer);
        begin
        print(b);
        n := n+1;
        print(b);
        b := b+5;
        end;
    a[1] := 10;
    a[2] := 20;
    a[3] := 30;
    a[4] := 40;
    n := 1;
    p(a[n+2]);
    new_line;
    print(a);
    end;

Output:

call by reference:  30 30 
                    10 20 35 40                    

call by name:       30 40
                    10 20 30 45

Examples of Parameter Passing in ML

ML uses call-by-value. When using the purely functional parts of ML, except for efficiency, the only time this gives a different result from call-by-name is if evaluating the argument gives an error or non-termination, and if the argument's value is never used. Example
fun f x = 42;

val ans = f (1 div 0);
In ML, this gives an error, since f evaluates its argument. If call-by-name were used, ans would just be bound to 42, since the argument isn't used.

We can simulate call-by-name in ML by wrapping the argument in a function:

fun g x = 42;
fun h x = x()+1;

val ans1 = g (fn() => 1 div 0);
val ans2 = h (fn() => 100);

Now consider passing a reference. We still use call-by-value, but in this case the value is a reference (that can be assigned into).

fun zeroit r = (r := 0);

val squid = ref 100;
zeroit squid;
val result = !squid;

Examples of Using Lazy Evaluation in Miranda

Miranda is a clean, purely functional language that uses lazy evaluation. You can run it on attu using the command 'mira'. See Miranda Basics for more information. (Don't panic! There might be something about the concept of lazy evaluation on the midterm, but nothing about the specifics of Miranda.)

The following code is also available as params.m

An 'if' function in Miranda. Since it uses lazy evaluation, the true or false branches only get evaluated if its value is needed.

my_if True x y = x
my_if False x y = y
Using the if function:
my_if (x>y) 3 4
my_if (x>y) 3 (1/0)
The infinite list of ones:
ones = 1 : ones
The infinite list of integers:
ints n = n : ints (n+1)
[1..]
The infinite string "abcabcabc ..."
lets = "abc" ++ lets
Two prime number programs:
factors n = [k | k <- [1..n]; n mod k = 0]

dullprimes = filter isprime [2..]
             where
             isprime p = (factors p = [1,p])


interestingprimes = sieve [2..]
                    where
                    sieve (p:x) = p : sieve [n | n <- x; n mod p > 0]
Hamming numbers. The Hamming numbers are all numbers of the form 2**i * 3**j * 5**k for all i,j,k >= 0. The ham function should return them in order, starting with 1.
my_merge (x:a) (y:b) = x : my_merge a (y:b) , if x<y
                     = y : my_merge a b , if x=y
                     = y : my_merge (x:a) b , otherwise

ham = 1: my_merge ham2 (my_merge ham3 ham5)

ham2 = map (*2) ham
ham3 = map (*3) ham
ham5 = map (*5) ham

Examples of Parameter Passing in Java

As noted above, Java uses call by value. For primitive types, this works exactly as described above. For objects, Java uses call-by-value with pointer semantics. Here are two examples, adapted from The Java Programming Language by Ken Arnold and James Gosling.

Passing a primitive type as a parameter:

class PassByValue {
  public static void main(String[] args) {
    double x = 1.0;
    System.out.println("before: x = " + x);
    halveIt(x);
    System.out.println("after: x = " + x);
  }

  public static void halveIt(double arg) {
    arg = arg/2.0;
    System.out.println("halved: arg = " + arg);
  }
}
Sample output:
before: x = 1.0
halved: arg = 0.5
after: x = 1.0
Here is another example, in which we pass an object as a parameter.
import java.awt.Point;

class PassObject {
  public static void main(String[] args) {
    Point p = new Point(10,20);
    System.out.println("before halveX: p.x = " + p.x);
    halveX(p);
    System.out.println("after halveX: p.x = " + p.x);

    setIt(p);
    System.out.println("after setIt: p.x = " + p.x);
  }

  public static void halveX(Point q) {
    q.x = q.x/2;
    System.out.println("halved: q.x = " + q.x);
  }

  public static void setIt(Point q) {
    q = new Point(0,0);
    System.out.println("setIt: q.x = " + q.x);
  }
}
Sample output:
before halveX: p.x = 10
halved: q.x = 5
after halveX: p.x = 5
setIt: q.x = 0
after setIt: p.x = 5