Today, we mainly discussed functions.

Recall also from last time:

**Extensional equivalence**.

Extensional
equivalence is an equivalence relation on well-typed SML expressions,
relating well-typed expressions of the same type.

Two expressions `e` and `e'` of the same base type
(such as `int`, `bool`, `char`, etc.)
are *extensionally equivalent*, written
`e ≅ e'`, whenever one of the following is true: (i)
evaluation of `e` produces the same value as does evaluation of
`e'`, or (ii) evaluation of `e` raises the same
exception as does evaluation of `e'`, or (iii) evaluation of
`e` and evaluation of `e'` both loop forever. Observe
that two values of a given base type are equivalent if and only if
they are identical. This definition generalizes naturally to more
complicated types (such as products and lists) constructed from base
types but not involving function types.

We further say that two function values `f : t ->
t'` and `g : t -> t'` of the same type are
extensionally equivalent whenever `f(v)`
and `g(w)` are extensionally equivalent for all extensionally
equivalent argument values `v` and `w` of
type `t`. Formally, `f ≅ g` if
and only if `f(v) ≅ g(w)` for all values
`v : t` and `w : t` for which
`v ≅ w`. Observe that function values can be
equivalent without being identical (see also below).

We can then generalize our definition of equivalence to say,
recursively, that two expressions `e` and `e'` of the
same type are extensionally equivalent whenever evaluation
of `e` and `e'`: (i) produces extensionally equivalent
values, or (ii) raises extensionally equivalent exceptions, or (iii)
loops forever for both expressions.

**Totality**.

We say that a function `f :
t -> t'` is *total* if and only if:
(i) `f` reduces to a value and
(ii) `f(v)` reduces to a value for every possible
argument value `v` of type `t`.

(When reading condition (i) bear in mind that `f`
could be a general expression of type `t -> t'` .)

Frequently, a proof of correctness may require establishing that
some expression reduces to a value. In order to accomplish that, it
may be useful to know that some function is total.

For instance,
if one wants to establish that the expression `x +
f(y)` reduces to a value, one approach is to show that
`x` and `y` reduce to values and
that `f` is a total function (possibly also that
`+` is total, though we usually take that for
granted in this course).

Totality is an important concept, at the
core of what it means to actually perform computations. Many
mathematical functions are not computable by total functions.

**Functions as 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. (The only nonlocal
variable here is the symbol `*`. Technically, the environment
includes a binding of symbol `*` to an internal multiplication
function. For simplicity, we generally do not specify bindings of
such built-in functions in this course, so we did not write 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`. (Again, for simplicity, we omit writing a binding for the
symbol `*`, but it too appears in the environment.)

**Comment:** We generally think of a function closure
as consisting of a lambda expression and the environment present at
the time of definition of the function. That is slightly different
from the definition just given, 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.

**NOTE:** 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/r](fn (x:int) => (s+r)*x)` *reduces
to* `(fn (x:int) => 5*x)`.

However, it is true
that these two function values are *extensionally equivalent*,
i.e., `[2/s, 3/r](fn (x:int) => (s+r)*x) ≅ (fn (x:int) =>
5*x)`.

Why? Because one may make the extensionally equivalent
*substitutions* of `2` for `s` and `3` for
`r` in the lambda expression `(fn (x:int) => (s+r)*x)`
to obtain the extensionally equivalent lambda expression `(fn
(x:int) => 5*x)` and then (by referential transparency) one may
use this simpler lambda expression in place of the original.

One
important point here is that SML does *not* perform the
mathematical operations `(s+r)*x` when the function is written,
only later when the function is called on (applied to) an argument
(see below).

**Be aware:** 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. Similarly, in code one simply writes function declarations or
lambda expressions directly. One does not and cannot write out the
environment as we have here, which we did purely for reasoning purposes.
Instead, the environment arises from the declarations and function calls
appearing in the code. It is implicit in the textual arrangement of the
code via SML's static lexical scoping rules. 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 for instance in this expression `(fn (x:int) => x*x)(7)`,
which applies the function `(fn (x:int) => x*x)` to argument
`7`.

We discussed function application. In order to evaluate `e2 e1`:

- Reduce
`e2`to a function value`f`of the form`fn(x:t)=>e`(along with the environment*env*that*existed at the time of definition*of the function). - Reduce
`e1`to a value`v`. - Locally extend environment
*env*with a binding of variable`x`to value`v`. - Evaluate the body
`e`of`f`in the resulting environment.

If that evaluation produces a value, return the value in the calling environment.

Once evaluation of

Example: The declaration

fun square (x:int) : int = x * xproduces a binding of variable

square(3+4) ==>(Hereenv(fn(x:int) => x*x)(3+4) ==>env(fn(x:int) => x*x)(7) ==>env[7/x]x*x ==>env[7/x]7*x ==>env[7/x]7*7 ==> 49

See also again the evaluation notes from the previous lecture.

**Five-Step Methodology.**

As a reminder, here again is the 5-step methodology, useful for writing functions:

- In the first line of comments, write the name and type of the function.
- In the second line of comments, specify via a
`REQUIRES`clause any assumptions about the arguments passed to the function. - In the third line of comments, specify via an
`ENSURES`clause what the function computes (what it returns). - Implement the function.
- Test your code.

**Patterns.**

We discussed patterns, and their use in clausal function definitions
and in `case` expressions.

These concepts are discussed further in the evaluation notes from the first lecture.

- Extensional Equivalence
- Functions
- Functions as values
- Anonymous lambda expression
- Closure
- Clausal function definitions
- Pattern matching
- 5-Step Methodology