CPS 343/543 Lecture notes: (Non-recursive) Procedures and closures



Coverage: [EOPL2] §3.5 (pp. 84-92)


Parameters vs. arguments

  • formal parameters (also known as bound variables or simply parameters)
  • actual parameters (often called arguments)
  • // defining function f
    // a and b are the formal parameters
    int f (int a, int b) {
       return (a+b); 
    }
    
    // invoking f
    // x and y are the actual parameters (arguments)
    f(x,y);
    


Adding procedures to our language

  • two new production rules and constructors on p. 84
  • sample expression on p. 84
  • we want procedures to be first class objects in the defined language
  • therefore, we want expressed value = denoted value = number + procval
  • what information must we include in the value of a procedure?
  • in order to determine this, let us examine what happens at procedure-application time


Closures

  • when a procedure is applied, its body is evaluated in an environment which binds the formal parameters to the actuals and binds the free variables in the body to their values in the enclosing environment at the time the procedure was created (called deep binding)
  • see example on p. 85
  • when f is called, its body should be evaluated in the following environment: {(y,2), (z, 28), (x,5)}
  • `in order for a procedure to retain the bindings of its free variables at the time it was created, it must be a closed package, [completely] independent of the environment in which it is used' [EOPL2] p. 85
  • this package is called a closure
  • a closure must contain:
    • the body of the procedure
    • the evaluation environment
      • binding of formal parameters to actual parameters
      • the bindings of its free variables from enclosing environment
  • a closure is a (code, environment) or (code, state) pair; remind you of an object in object-oriented programming?
  • every language has closures, but only some (e.g., Scheme) give the programmer access to them (i.e., closures are only first-class entities in some languages)
  • `we say that this procedure is closed over or closed in its creation environment' [EOPL2] p. 85
  • therefore, let us think of a procedure value as an ADT with the following interface:
    • closure (builds a procedure value)
    • apply-procval (applies a procedure value)
  • must satisfy the following equation:
    (apply-procval (closure ids body env) args) =
    (eval-expression body (extend-env ids args env))
    
  • closure representation
    • procedural closure representation on pp. 85-86
    • abstract syntax closure representation on pp. 86


Augmenting eval-expression

  • now with this foundation in place, we can easily modify our interpreter to handled first-class procedures
  • new code for eval-expression on p. 87
  • augmented eval-expression in Fig. 3.7 on p. 88
  • sample expression evaluation example on pp. 88-89
  • again, identifiers for variables need not appear in syntax trees manipulated by an interpreter [EOPL2]
  • if we use the ribcage representation for an environment, then the lexical address of a variable reference v (d p) tells us exactly where the variable reference appears: the d-th rib at position p [EOPL2]


Deep, Shallow, and Ad-hoc Binding

referencing environment for passed subprogram (function)
  • deep binding: uses the environment at the time the passed function was created (the default in our defined language) (line a below)
  • shallow binding: uses the environment of the expression which invokes the passed function (line b below)
  • ad hoc binding: uses the environment of the invocation expression in which the subprogram is passed as an argument (line c below)
  • example:
    let
       y = 3
    in
       let
          x = 10
          f = proc (x) *(y, +(x,x)) ; line a
       in
          let
             y = 4
          in
             let
                y = 5
                x = 6
                g = proc (x, y) *(y, (x y)) ; line b
             in
                let
                   y = 2
                in
                   (g f x) ; line c
    
    results:
    • deep binding: 216
    • shallow binding: 288
    • ad-hoc binding: 144
  • We use deep binding in our defining language (i.e., our interpreter implements deep binding).

    When McCarthy and his students at MIT were developing the first version of LISP, they really wanted static scoping, but implemented pure dynamic scoping by accident.

    Their second version of LISP (the patchversion) also did not implement static scoping, but rather ad-hoc binding, which is closer to dynamic scoping, but still not quite pure dynamic scoping.

    Dubbed the (downward) funarg problem (i.e., functional argument problem). The upward funarg problem, which is more difficult, involves return functions to functions (rather than passing functions to functions).


The BIG picture

  • we have so carefully designed our ADT's through interfaces that now our interpreter is very flexible
  • programming language concepts (on which this course focuses) often have options:
    • scoping (static or dynamic)
    • binding (deep, shallow, ad-hoc)
  • we can often alter the semantics of the defined language drastically (e.g., from static to dynamic scoping) by changing as little as 1-2 lines of code in the interpreter (usually just change how and when we pass the environment) (e.g., deep, shallow, or ad-hoc binding approaches).
  • again, identifiers for variables need not appear in syntax trees processed by an interpreter (depth and position are all we need)
  • in summary, the interpreter for a language is nothing more than another program [EOPL2] Forward


References

    [EOPL2] D.P. Friedman, M. Wand, and C.T. Haynes. Essentials of Programming Languages. MIT Press, Cambridge, MA, Second edition, 2001.

Return Home