20-CS-4003-001 Organization of Programming Languages Fall 2017
Parameter Passing

Lambda calculus, Type theory, Formal semantics, Program analysis

    Prev     Next     All lectures

Non-Strict Evaluation

Scheme:

  (define-syntax swap!
    (syntax-rules ()
      ((swap! a b)
       (let ((c a))
         (set! a b)
         (set! b c)))))

  (define a 1)
  (define b 2)
  (define c 3)
  (swap! c b)

  (define succ
    (lambda (x)
      (cons x (lambda () (succ (+ x 1))))))

  (define take
    (lambda (n s$)
      (if (zero? n)
          '()
          (cons (car s$) (take (- n 1) ((cdr s$)))))))

Haskell:

  (take 10 [1..])

Java:

  import java.lang.reflect.*;

  class b {
     public b () { }
     public void op (int a, int b) {
        System.out.println(a+b);
     }
  }

  class c {
     public c () { }
     public void op (int a, int b) {
        System.out.println(a-b);
     }
  }

  public class a {
     public static void exec (String mh, int a, int b) {
        try {
           Class [] argtypes =
              new Class[] { Integer.TYPE, Integer.TYPE };
           Class <?> cls = Class.forName("b");
           Constructor ct = cls.getConstructor();
           Method m = cls.getMethod(mh, argtypes);
           Object [] args =
              new Object[] { new Integer(a),
                             new Integer(b) };
           m.invoke(ct.newInstance(), args);
           cls = Class.forName("c");
           ct = cls.getConstructor();
           m = cls.getMethod(meth, argtypes);
           m.invoke(ct.newInstance(), args);
        } catch (Exception e) { }
     }

     public static void main (String args[]) {
        String meth = args[0];
        int a = Integer.parseInt(args[1]);
        int b = Integer.parseInt(args[2]);
        exec(meth, a, b);
     }
  }

C:

  #include <stdio.h>
  #define swap_(a,b) { int c = a; a = b; b = c; }

  int main () {
     int a=1, b=2, c=3, d=2, e=3;
     swap_(c,b);
     printf("a=%d, b=%d, c=%d, d=%d, e=%d\n",
             a,b,c,d,e);
     swap_(e,d);
     printf("a=%d, b=%d, c=%d, d=%d, e=%d\n",
             a,b,c,d,e);
  }

  /* swap_1 - call-by-name */
  #include <stdio.h>

  int A[] = { 1,2,3,4,5,6,7,8 };
  int i, j;

  /* for computing A[i] */
  int fi () {  return A[i];  }

  /* for computing i=A[i] */
  int fj () {  i = A[i]; return i;  }

  /* swap procedure takes procedures as arguments */
  int swap (int(*f)(), int(*g)()) {
     int temp = A[f()];
     A[f()] = A[g()];
     A[g()] = temp;
  }

  int main () {
     int k;
     i = 3;
     /* objective: swap(A[i],i=A[i]) */
     swap (fi,fj);
     for (k=0 ; k < 8 ; k++) printf("%d ",A[k]);
     printf("\ni=%d\n",i);
  }

  /* swap_2 - call-by-value */
  #include <stdio.h>

  int A[] = { 1,2,3,4,5,6,7,8 };
  int i, j;

  int swap_2 (int i, int j) {
     int temp = A[i];
     A[i] = A[j];
     A[j] = temp;
  }

  int main () {
     int k;
     i = 3;
     swap (A[i],i=A[i]);
     for (k=0 ; k < 8 ; k++) printf("%d ",A[k]);
     printf("\ni=%d\n",i);
  }

 -  In non-strict languages procedure arguments are not evaluated until they are needed when the procedure is executed. There are several ways this can be achieved.

Normal order: outermost reducible expressions in the body of a procedure are reduced and evaluation of arguments is delayed until they are needed to complete the reductions.

Call by name: arguments are substituted directly in the procedure body and are evaluated as those substitutions are executed. The substitutions are such as to avoid the possibility of using variable names that are in the body of the procedure and may require local renaming (capture-avoiding substitution). Observe that a substitution may be evaluated several times or not at all. A substitution does not necessarily have the same value each time it is executed in the body. The Java program to the right illustrates call-by-name: a method name op is passed as a parameter to exec and it is applied twice to the same operands with different results. Call-by-name is typically implemented by passing the address of a procedure that computes the argument and this procedure is applied each time the argument's substitution is executed. This is illustrated in the C program swap_1 where fi and fj are procedures for computing A[i] and i=A[i], respectively. The output of this program is. 1 2 3 4 5 5 7 8. The call-by-value version of this program is swap_2 where the output is 1 2 3 4 6 5 7 8.

Call by need: a memoized version of call-by-name. The procedure used to evaluate the substitution is saved for future use - this procedure is likely changed from call to call. The Haskell program to the left has an argument which should evaluate to an infinite list. If [1..] is entered in the ghci Haskell interpreter it will print 1,2,3.. indefinitely. However, take asks for only the first 10 of the numbers in this list and those are only the ones that are computed. The same principle is illustrated plainly in the Scheme programs succ and take where (succ 1) is a number-procedure pair where the procedure is capable of producing the (infinite) rest of the list past the number. Procedure take merely invokes that procedure 10 times, each time replacing the procedure with the same one except with different state.

Call by macro expansion: similar to call-by-name but uses textual substitution. This means variable substitution conflicts may arise unless hygienic macros are used. Scheme uses hygienic macros. The swap! macro in Scheme illustrates this: the values of b and c are 3 and 2, respectively, after (swap! c b) (they were 2 and 3, respectively, before). The C language does not support hygienic macros. A swap_ macro implementation and use in C, similar to the swap! in the Scheme example, is shown to the left. The result of the first printf is a=1, b=2, c=3, d=2, e=3 and the result of the second is a=1, b=2, c=3, d=3, e=2. Thus the macro worked for swapping d and e but not for swapping b and c.