# let x = 3;;
        val x : int = 3
        # let y = 17;;
        val y : int = 17
        # let f(n) = 2 * n;;
        val f : int -> int = <fun>
These three definitions introduce three bindings in the top-level
environment for variables x and y and a function called f.  We have
also seen let expressions and it is important to understand that they
do not introduce bindings into the top-level environment, as in:
        # let z = 19 in x + y + z;;
        - : int = 39
        # z;;
        Error: Unbound value z
A let expression entered at the top level introduces its own scope
that is inside of the global scope:
        +-------------------------------------+
        |   let x = 3                         |
        |   let y = 17                        |
        |   let f(n) = 2 * n                  |
        | +---------------------------------+ |
        | | let z = 19 in x + y + z         | |
        | +---------------------------------+ |
        +-------------------------------------+
I revisited an example from the previous lecture where we considered
what value the following code would produce:
        let y = 2
        let f(n) =
            let x =
                let n = 3
                in 10 * (n + y)
            and y = 100 * n
            in x + y + n
        
        f(10)
The call on f(10) evaluates to 1060, as we saw in the previous
lecture.  I showed this picture of the various scopes being
introduced:
        +-------------------------------------+
        |   let y = 2                         |
        | +---------------------------------+ |
        | | let f(n) =                      | |
        | |   +---------------------------+ | |
        | |   | let x =                   | | |
        | |   |       +-----------------+ | | |
        | |   |       | let n = 3       | | | |
        | |   |       | in 10 * (n + y) | | | |
        | |   |       +-----------------+ | | |
        | |   | and y = 100 * n           | | |
        | |   | in x + y + n              | | |
        | |   +---------------------------+ | |
        | +---------------------------------+ |
        |                                     |
        |   f(10)                             |
        +-------------------------------------+
This code introduces two bindings at the top level: one for a variable
y with the value 2 and one for a function called f.  All of the other
bindings introduced here are in inner scopes.  I mentioned that I
really should somehow indicate that the parameter n for function f is
inside its scope along with the let expression that defines local
variables x and y.Remember that the rules of scope are that you use local definitions when they exist and otherwise you repeatedly look to the containing scope and if you don't find the identifier there you look to its containing scope and so on.
The interpretation of y in computing 10 * (n + y) is unusual in that normally we would look to the containing scope which has a binding of y to 100 * n. But order matters and that definition of y comes after this expression, so we instead look outward to find the global binding of y to 2. This is sometimes referred to as a hole in scope. Java has the same issue, as in this example:
        public class Test {
           public static int x = 3;
                
            public static void main(String[] args) {
                System.out.println("x = " + x);
                int x  = 17;
                System.out.println("x = " + x);
            }
        }
This program produces the following output:
        x = 3
        x = 17
Notice that within the same scope the identifier x is interpreted in
two different ways because the first println appears before the local
definition of x
I mentioned on the first lecture that an OCaml program is thought of
as a series of bindings introduced by let definitions.  One way of
understanding the difference between a let definition and a let
expression is to think of each let definition as introducing a nested
let expression:
         let x = 3 in
             let y = 17 in
                let f(n) = 2 * n in
                    ...
We know by the rules of static scope that the most local definition
takes precedence, so when we introduce a new binding for a variable or
function, that new binding is used in place of the old binding.Then I spent a few minutes discussing arrays in OCaml. Arrays are like lists with two important differences: they have a fixed size and the elements are mutable. You can define an array by surrounding a sequence of values with vertical bar characters inside of brackets:
        # let a = [| 38; 42; 75 |];;
        val a : int array = [|38; 42; 75|]
There is a length function for an array just as with lists, but it
runs in O(1) time instead of O(n) time:
        # Array.length(a);;
        - : int = 3
And there are versions of familiar functions like map that can be used
to manipulate an array.  The function Array.map returns a new array
without changing the original array:
        # let a2 = Array.map (fun x -> 2 * x) a;;
        val a2 : int array = [|76; 84; 150|]
        # a;;
        - : int array = [|38; 42; 75|]
You can refer to individual elements by using the notation .(i) where
i is an index.  You can change an array's value by using the <-
operator, as in:
        # a.(0) <- 100;;
        - : unit = ()
        # a;;  
        - : int array = [|100; 42; 75|]
In functional programming we generally try to avoid what are called
side effects.  In an earlier lecture I asked when you would know that
given a function f that returns an int that you can replace:
        f(n) + f(n)
with:
        2 * f(n)
The answer is that you can make this substition if the function f has
no side effects.  This property is known as referential
transparency.Notice that the mutating expression indicated a return type of "unit = ()". The unit type is used in OCaml for expressions that don't evaluate to anything. These expressions are only useful if they produce some kind of side effect like mutating a value or printing something. Here is another example:
        # print_string("Hello world!\n");;
        Hello world!
        - : unit = ()
This function had the side effect of printing a line of output, but it
didn't return anything.  OCaml has a special rule for expressions of
type unit that you can chain them together separated by semicolons, as
in:
        # print_string("hello\n"); print_endline("there");;
        hello
        there
        - : unit = ()
The print_endline function automatically includes an end-of-line after
the string it is printing.  You can also have an actual computation as
the last element in such a sequence:
        # print_endline("hello"); 2 * 3 + 4;;
        hello
        - : int = 10
Notice that here the expression evaluates to the value 10 of type
int.I reminded people of the following code we looked at in the previous lecture:
        let x = 3
        let f(n) = x * n
        f(8)
        let x = 5
        f(8)
The definition of function f refers to n, which is defined in the
function itself (the parameter passed to it), but f also refers to a
variable that is not defined in the function itself, the variable x.
Variables that are not defined within a function's scope are referred
to as free variables.We found that the function uses the binding of x to 3 that exists when the function is defined. Changing the binding for x does not change the behavior of the function. My question is, how does that work? How does OCaml manage to figure that out?
The answer involves understanding two important concepts:
Remember that functions can have free variables in them, as in our function f that refers to a variable x that is not defined inside the function. The idea of a closure is that we attach a context to the code in the body of the function to "close" all of these stray references.
We explored some examples to understand the difference between a let definition that fully evaluates the code included in the definition versus a function definition that delays evaluating the code used in the definition. For example, I included some expressions that included calls on printing functions to show that let definitions are fully evaluated.
        # let x = 3;;
        val x : int = 3
        # let y = print_endline("hello"); 2 * x;;
        hello
        val y : int = 6
        # let f1(n) = print_endline("hello"); 2 * x + n;;
        val f1 : int -> int = <fun>
        # f1(3);;
        hello
        - : int = 9
        # f1(10);;
        hello
        - : int = 16
For a let definition, the print is performed when you type in the definition
(indicating that OCaml is evaluating the code at that time).  For the
function definition, the print happens only when the function is
called, indicating that OCaml delayed evaluating the expression until
individual calls are made.I gave one more example of a function definition to really understand the implications of what it means to have a closure.
        # let a = [|17; 45; 9|];;
        val a : int array = [|17; 45; 9|]
        # let b = 100;;
        val b : int = 100
        # let f(c) = print_endline("hello"); a.(0) + b + c;;
        val f : int -> int = <fun>
        # f(8);;
        hello
        - : int = 125
We begin by defining an array called a whose 0 element has the value
17.  We then define a variable b with the value 100.  And then we
define a function f that prints a line of output with "hello" and then
returns the sum of the zero-element of a, b, and c.  In this case, the
free variables are a and b.  The parameter c is defined within the
function's scope.  When we call it passing 8 to c, we get the result
125 (17 + 100 + 8).We know that changing b will not change the behavior of the function because it keeps track of the environment that existed at the point that we defined it. But what about the array a? Because the array is mutable, we can change that value and that changes the behavior of the function:
        # b = 0;;
        - : bool = false
        # let b = 0;;
        val b : int = 0
        # a.(0) <- 10;;
        - : unit = ()
        # f(8);;
        hello
        - : int = 118
As expected, resetting b to 0 changed nothing.  But resetting the
zero-index element of a to 10 changed the computation.  Now the
function prints the line of output and returns 118 (10 + 100 + 8).
This points out an important property of mutable state, that it can
lead functions to change their behavior.I then spent some time discussing why it is difficult in a language like Java to create a closure. I first showed some code that used inheritance to define a variation of the Stack class that overrides the push method to call super.push twice:
        import java.util.*;
        
        class MyStack extends Stack<Integer> {
            public Integer push(Integer n) {
                super.push(n);
                return super.push(n);
            }
        }
        
        public class StackStuff {
            public static void main(String[] args) {
                Stack<Integer> s = new Stack<>();
                s.push(8);
                s.push(10);
                s.push(17);
                s.push(24);
                while (!s.isEmpty()) {
                    System.out.println(s.pop());
                }
            }
        }
When we compiled and ran it, we could see that every value was being duplicated
in the stack:
        24
        24
        17
        17
        10
        10
        8
        8
I mentioned that in Java you can define an anonymous inner class that uses
inheritance the same way that the MyStack class does:
        import java.util.*;
        
        public class StackStuff2 {
            public static void main(String[] args) {
                Stack<Integer> s = new Stack<>() {
                    public Integer push(Integer n) {
                        super.push(n);
                        return super.push(n);
                    }
                };
                s.push(8);
                s.push(10);
                s.push(17);
                s.push(24);
                while (!s.isEmpty()) {
                    System.out.println(s.pop());
                }
            }
        }
This class had the same behavior.  But what if we introduce a local variable
and try to access it inside the inner class?  That would introduce a free
variable.
        public static void main(String[] args) {
	    int x = 3;
            Stack<Integer> s = new Stack<>() {
                public Integer push(Integer n) {
		    super.push(x);
                    super.push(n);
                    return super.push(n);
                }
            };
            ...
This is very tricky because x is a stack-allocated variable.  How can the inner
class preserve access to it?  It would be even worse if this code appeared in a
method called by main that returned a reference to the stack that it has
constructed.  That method's local variables would be freed up, so how could the
stack keep a reference to a variable that no longer exists?But this compiled and ran and behaved as expected by pushing a 3 on top of the stack along with two copies of each value. That would seem to be a closure. This inner class seems to be preserving the environment in which it was defined, including that free variable x that was a local variable in main. But looks can be deceiving. I asked if anyone could think of a way to test whether it really is keeping track of that local variable and someone suggested that we could change x later in the program:
        public static void main(String[] args) {
            int x = 3;
            Stack<Integer> s = new Stack<>() {
                public Integer push(Integer n) {
                    super.push(x);
                    super.push(n);
                    return super.push(n);
                }
            };
            s.push(8);
	    x = 10;
            s.push(10);
            ...
This program did not compile.  The compiler said:
        StackStuff2.java:8: error: local variables referenced from an inner
        class must be final or effectively final
In other words, Java is cheating.  It treated our local variable x as if it
were a constant.  That means it wasn't a free variable at all.  Java does not
have true closures.  C#, on the other hand, has true closures and there is a
lot of work that goes into making that work.This is yet another example of mutable state causing difficulty that immutability resolves. It's difficult to provide access to a local variable of a method, but it's not difficult to provide access to an immutable constant.