We discussed functions today.
Functions are values.
A function value consists of an anonymous lambda expression along with a (possibly empty) environment of bindings for any nonlocal variables that appear in the body of the function.
The combination of a lambda expression and an environment is called a closure.
Example 1: The anonymous lambda expression (fn (x:int) => x*x) is a function that squares its argument. (There are no nonlocal variables in the body of this function, so we don't need to specify an environment.)
Example 2: [3.14159265358979/pi](fn (r:real) => pi*r*r) is an environment and an anonymous function that together provide a function for computing the area of a disk of radius r.
NOTE carefully: A lambda expression (plus any bindings of nonlocal
variables) is a value.
A value is a value; one cannot reduce a value further.
So one would not say that
[2/s, 3/t](fn (x:int) => (s+t)*x) ==> (fn (x:int) => 5*x).
However, it is true that these two function values are extensionally equivalent,
i.e., [2/s, 3/t](fn (x:int) => (s+t)*x) ≅ (fn (x:int) => 5*x).
Also: When SML prints a function value in the REPL, it will merely print fn (plus the type of the function), not all the internal details of the closure. Only in proofs or similar reasoning do we use the written representation shown above. And of course, you can't directly write a closure inline in code. If you want bindings in the environment, you have to create those using declarations. However, you can write anonymous lambda expressions inline in your code, as in (fn (x:int) => x*x)(7), for instance.
We discussed function application. In order to evaluate e1 e2:
fun square (x:int) : int = x * xproduces a binding of a function value to the variable square. Then:
square(3+4) ==> (fn(x:int) => x*x)(3+4) ==> (fn(x:int) => x*x)(7) ==> [7/x]x*x ==> 7*7 ==> 49
See also again the evaluation notes from the previous lecture.
Side comment: In class we thought of a function closure as consisting of a lambda expression and the environment in effect at the time of definition of the function. That is slightly different than the definition given above, since the environment at the time of definition may contain more bindings than are necessary to resolve the values of any nonlocal variables appearing in the body of the function. However, the two definitions amount to the same thing operationally, since those extra bindings never matter.
We introduced the 5-step methodology for writing functions:
We introduced case expressions.
We introduced clausal function definitions based on pattern matching.
We also started in on next lecture's topic (induction) by proving that the our implementation of fact always returns a value for nonnegative arguments.