CSE341 Notes for Friday, 4/17/24

I began by asking people about a certain behavior that you see in databases and spreadsheets. Suppose that you have a column of midterm scores and you leave two cells empty because the students didn't take the exam. If you then compute the average of that column of numbers, are the two empty cells included? Everyone seemed to know that they are not included. So if you change those cells to have a 0, you get a different average.

I mentioned that I had heard an interesting talk by Anders Heljsberg, the designer of the C# programming language in which he described Microsoft's decision to include nullable types in C# 2.0. He said they did this to make it easy for C# programs to interoperate with SQL data (databases).

I said that in C# 2.0, you can declare a variant of int called int? that is a nullable type of int. I asked what that might look like and someone said it sounds like an int that can be set to null. That's exactly right. In version 2.0 of C# you can say things like:

        int? x;
        x = null;
Why would you want to do that? Anders said that Microsoft was doing this to allow C# code to more easily interoperate with SQL applications. Anyone who uses Excel should be familiar with this concept. There is a difference between leaving a data cell blank versus filling in a value like 0. I often have to yell at the TAs not to enter a 0 unless a student earned an actual score of 0. Otherwise our computations for values like minimum, median, and average will give misleading results.

So then I asked people if this reminded them of anything. Does Microsoft's "int?" correspond to anything in OCaml? Someone said it's an int option, which is right. OCaml's option type allows us to create nullable types. Of course, then you have to either use pattern matching or Option.get to pull a value back out of an option type, which is a bit of a pain. Microsoft has decided to have the compiler do a lot of the work for you to implicitly call the equivalent of Option.get and the equivalent of the Some constructor to "wrap" a value into an option.

One of the things I like about OCaml is that it is usually possible to write clean, simple code without having to redesign the language or to build such support into the compiler. The option type is easy to define in terms of standard OCaml:

        type 'a option = None | Some of 'a;
The get function is also easy to write using standard OCaml:

        let get(opt) = 
            match opt with
             | None       -> invalid_arg("option is None")
             | Some value -> value
Then I askd people to consider this code:

        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:

So in OCaml we really should think of function definitions as being a pair of things: some code to be evaluated when the function is called and an environment to use in executing that code. This pair has a name. We refer to this as the closure of a function.

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.

Then I started a new topic: lexical scope versus dynamic scope. This is just one example of a number of related topics that have to do with the static properties of a program versus the dynamic properties of a program. The terms compile time and run time are related terms because we can think of these as the static properties that can be deduced ahead of time by a program like a compiler versus the dynamic properties that are apparent only when the program actually executes.

Lexical scope is a static property, which is why it is sometimes referred to as static scope (e.g., in the wikipedia entry about scope). Lexical scope will be familiar because Java uses it. Consider, for example, the following program:

        public class Scope {
            public static int x = 10;
                
            public static void main(String[] args) {
                System.out.println(x);
                if (x > 0) {
                    int x = 20;
                    System.out.println(x);
                }
                int x = -30;
                System.out.println(x);
            }
        }
In Java, every set of curly braces introduces a new lexical scope (a new region of the program known as a block). In the program above, there is an outer scope for the overall class and an inner scope for method main and for the if:

and inner scopes for each of the three methods:


        +-------------------------------------------------+
        | public class Scope {                            |
        |     public static int x = 10;                   |
        |                            +------------------+ |
        |     public static void main|(String[] args) { | |
        |   +------------------------+                  | |
        |   |     System.out.println(x);                | |
        |   |               +----------------+          | |
        |   |     if (x > 0)| {              |          | |
        |   |   +-----------+                |          | |
        |   |   |     int x = 20;            |          | |
        |   |   |     System.out.println(x); |          | |
        |   |   | }                          |          | |
        |   |   +----------------------------+          | |
        |   |     int x = -30;                          | |
        |   |     System.out.println(x);                | |
        |   | }                                         | |
        |   +-------------------------------------------+ |
        | }                                               |
        +-------------------------------------------------+
We want to pay attention to the identifier "x" as it is used in each of these scopes. There is a global variable x declared in the outer scope that is printed by the first call on println. There is a local x that shadows the global x that is defined inside the if block and is printed by the second println. And there is a different local x that shadows the global x and that is printed by the third println. So the output of this program is 10, 20, -30.

I then asked people to think about how scope works in OCaml. OCaml uses lexical scoping, but where do the scopes come from? Function declarations introduce a scope for the parameters that are listed for the function, as in:

        let f(m, n) = [this creates a local scope for identifiers m and n]
        (* m and n are not visible here after the function definition *)
        fun x -> [this creates a local scope for identifier x]
We also get a different local scope for each let construct. You can think of each "let" as introducing its own scope box. Because there can be let constructs inside of let constructs, we can have scopes inside of scopes.

I then briefly discussed the idea of dynamic scope. Dynamic scope is less common in programming languages today. If you're writing an interpreter, it's easier to do dynamic scoping, so it was common in the early days for interpretted languages to use it. But most people found it sufficiently confusing that it is rarely used in modern languages.

I mentioned that I'm not expecting everyone to master the dynamic scope approach. But there are some languages that still use it. For example, most shell scripts use dynamic scope. I asked people to consider this script:

        #!/bin/sh
                
        x="hello"
                
        foo()
        {
            echo $x
        }
                
        bar()
        {
            local x="foo"
            foo
        }
                
        foo
        bar
        echo $x
Using lexical scope rules, we would expect that foo always prints the global x:

        +-----------------------+
        | #!/bin/sh             |
        |                       |
        | x="hello"             |
        |                       |
        | foo()                 |
        | +-------------+       |
        | | {           |       |
        | |     echo $x |       |
        | | }           |       |
        | +-------------+       |
        |                       |
        | bar()                 |
        | +-------------------+ |
        | | {                 | |
        | |     local x="foo" | |
        | |     foo           | |
        | | }                 | |
        | +-------------------+ |
        |                       |
        | foo                   |
        | bar                   |
        | echo $x               |
        +-----------------------+
But the shell script uses dynamic scope. In dynamic scope, you keep track of the commands in the order that they are executed. Each time you call a function, that opens a new dynamic scope. So the scopes that are generated by this script are quite different from the lexical scope:

        +-----------------------+
        | #!/bin/sh             |
        |                       |
        | x="hello"             |
        |                       |
        | foo                   |
        | +-------------+       |
        | | {           |       |
        | |     echo $x |       |
        | | }           |       |
        | +-------------+       |
        |
        | bar                   |
        | +-------------------+ |
        | | {                 | |
        | |     local x="foo" | |
        | |     foo           | |
        | |     +-------------+ |
        | |     | {           | |
        | |     |     echo $x | |
        | |     | }           | |
        | |     +-------------+ |
        | | }                   |
        | +-------------------+ |
        |                       |
        | echo $x               |
        +-----------------------+
Notice that when we call bar and it calls foo, that creates an inner dynamic scope for foo. When it goes to echo $x, it looks to the containing dynamic scope for bar and finds a different definition for $x than in the simple call on foo. So this script produces the following output:

        hello
        foo
        hello
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 MyStack();
                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.


Stuart Reges
Last modified: Thu Apr 18 13:42:28 PDT 2024