Critical point: a closure's environment pointer refers to the environment that encloses the point of function definition, not the point of function application. So far, we haven't really exploited this much, but it is very important for higher-order functions.
When you pass a function as a parameter, its values do not get rebound:
val a = 15; fun addA(k) = k + a; fun doAction(f, a) = f(a); (* At application of doAction, notice that the a in addA will still refer to the a from the global scope---not the parameter a in doAction. The result of this expression is 18, not 6. *) val aPlus3 = doAction(addA, 3);
Exercise: Can you draw the environment at each step of the evaluation of the code above?
Consider the function makeAddX(x) which we discussed last week:
fun makeAddX(x) = fn y => x + y; val add5 = makeAddX 5; val eleven = add5 6;
makeAddX returns a function that uses a value from its enclosing scope. The activation environment will persist until it is no longer reachable. In other words, activations are allocated on the heap and may have indeterminate lifetimes.
Exercise: Can you draw the environment at each step of the evaluation of the above lines?
You may have noticed that function application and let-expressions both produce a new environment with bindings. Perhaps this doesn't set off your non-orthogonality detector, but it should: why have two mechanisms when you can have one?
It turns out that all let expressions can be rewritten using only anonymous functions! Can you see how? Try to rewrite the following using only anonymous functions and function application:
let val x = 3; val y = 4; in x + y end;