CSE341 Notes for Wednesday, 1/31/07

I began the lecture by discussing two things that have turned out to be surprises for me. I was surprised that my tangent into discussing dynamic versus lexical scope has taken so much time and I was surprised to find that there isn't much more of ML for me to teach and include in programming assignments. I chalk this up to the fact that I'm teaching the course for the first time and I'm still trying to figure out the right pace for the material.

I said that since I had already dug such a deep hole for myself with scope that I might as well light a couple of sticks of dynamite while I'm down there. I can't end up much deeper and perhaps it will lead to an interesting place.

So first I revisited the question of static versus dynamic properties of programs. I have been keeping a 2-column list of various properties we have discussed:

        static                    dynamic
        compile-time              run-time
        compilers                 interpreters
        static type checking      dynamic type checking
        lexical scope             dynamic scope
                                  dynamic behavior
                                  control flow (what calls what)
I decided to discuss scope as a way to explore this dichotomy. The problem is that we've ended at a place where most people seem to think that dynamic scope is not very useful. The best thing we've been able to say about it is that it would be easier to implement if you were writing an interpreter because all you'd have to do would be to look down the call stack to find the most recent definition of a variable.

I suspect that if the students in the class had been raised on dynamic scope, you'd probably like it more (and a few people have posted to the message board to reinforce this idea). But the fact is that few programming languages use it, which is an indication that most of the community have come to the conclusion that it is not as easy to understand as lexical scope.

But I don't want people to draw the wrong conclusion from this. So I decided to light my first stick of dynamite by talking about another difference between static and dynamic properties of a program. What about method binding? What I mean by that is figuring out what method is called when a program is executed.

I typed in the following Java code to define a class called Data1 with two methods in it and a testing class with a main method:

        class Data1 {
            public void one() {
        	System.out.println("one");
        	two();
            }
        
            public void two() {
        	System.out.println("two");
            }
        }
        
        public class Test {
            public static void main(String[] args) {
        	Data1 x = new Data1();
        	x.one();
        	System.out.println();
        	x.two();
            }
        }
As expected, the program produces the following output:

        one
        two
        
        two
The question I posed was to think about the call on the method two from inside method one. Using lexical scope, we know that it's a reference to method two, so isn't it the case that it always produces two as output?

        class Data1 {
            public void one() {
        	System.out.println("one");
        	two();  // always produces "two" as output?
            }
        
            public void two() {
        	System.out.println("two");
            }
        }
The simple approach would be to say that this is true, that the call on method two always is a call on the method we see here. But we know that it's not that simple because of inheritance. For example, if we define a new class that extends the original:

        class Data2 extends Data1 {
            public void two() {
        	System.out.println("You dirty liar!");
            }
        }
Then when we call method one on a Data2 object, we will get output like this:

        one
        You dirty liar!
This is an important property of Java that we call polymorphism. It has many other names including dynamic binding, late binding, runtime binding and dynamic dispatch. So this single line of code in the Data1 class can lead to several different methods being executed. This is similar to the dynamic scope example we had where a single reference to a variable x could end up referring to several different variables.

This is an issue not just inside a class with inheritance. Consider, for example, this modification to the testing program:

        public class Test {
            public static void main(String[] args) {
        	doIt(new Data1());
                System.out.println();
        	doIt(new Data2());
            }
        
            public static void doIt(Data1 x) {
        	x.one();
        	System.out.println();
        	x.two();
            }
        }
The call on "x.two()" in the doIt method ends up calling two different methods. When it is passed a Data1 object, it calls the method that prints "two". When it is passed a Data2 object, it calls the method that prints "You dirty liar!". We saw that this program produces the following output:

        one
        two
        
        two

        one
        You dirty liar!

        You dirty liar!
We end up with two examples of polymorphism here: one in the Data1 class and one in the testing program. In both cases a single call on a method called two generates two different results. And it's even more complicated than that in that we could define even more classes that inherit from Data1 and that define their own versions of method two.

The idea behind calling this dynamic dispatch is that at runtime you need some kind of dispatching code that figures out which method to call depending upon the type of object that is in use.

So in the case of dynamic scope, the general feeling seemed to be that it's confusing and not worth it. Why is it worth it to do dynamic binding? Some people think it isn't. In C++ you'd have to declare a method as "virtual" to get this behavior. The designers of C# decided that the default behavior of methods in C# would be static unless you attach the same "virtual" keyword to a method. So in those languages you have to go out of your way to get dynamic binding.

For the designers of Java the feeling was that dynamic binding should be the default to keep the language simple and because inheritance is so powerful.

I then discussed in more detail some examples from Monday's lecture. We have looked at this example:

        val x = 3;
        fun f(n) = x * n;
        f(8);
        val x = 5;
        f(8);
As we've seen, 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 ML manage to figure that out?

The answer involves understanding two important concepts:

So in ML 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 val declaration that fully evaluates the code included in the declaration versus a function definition that delays evaluating the code used in the definition. For example, I included some sequence expressions with calls on print to show that val declarations are fully evaluated.

        - val x = 3;
        val x = 3 : int
        - val y = (print("hello\n"); 2 * x);
        hello
        val y = 6 : int
        - fun f1(n) = (print("hello\n"); 2 * x);
        val f1 = fn : 'a -> int
        - f1(3);
        hello
        val it = 6 : int
        - f1(10);
        hello
        val it = 6 : int
For a val definition, the print is performed when you type in the definition (indicating that ML is evaluating the code at that time). For the function definition, the print happens only when the function is called, indicating that ML delayed evaluating the expression until individual calls are made.

This happens even with a val declaration that uses a curried function to define another function:

        - fun multiply x y = x * y;
        val multiply = fn : int -> int -> int
        - val double = sum (print("hello2\n"); 2);
        hello2
        val double = fn : int -> int
        - double(3);
        val it = 5 : int
        - double(4);
        val it = 6 : int
Of course, if the print had been included in the original function definition, then it would be delayed as well. The key idea is that the function definition is creating a closure the includes code to be evaluated later along with an environment (a set of bindings) to use in evaluating that code.

I then spent the last few minutes of class setting off my second stick of dynamite by talking about the idea of closures as they relate to Java. In Java, you can have an inner class that makes reference to fields and methods from the other class, as in:

        class Outer {
            private int x;
        
            public class Inner {
        	public void doSomething() {
        	    x++;
        	}
            }
        
            public Outer() {
        	x = 5;
        	Inner i = new Inner();
        	i.doSomething();
        	i.doSomething();
        	System.out.println(x);
            }
        }
        
        public class Test2 {
            public static void main(String[] args) {
        	new Outer();
            }
        }
This program prints 7 as its output. The outer object sets its field to 5 but it also constructs an object of type Inner that increments the variable twice. How is this done? How can an object of an inner class change something in the outer class? The answer is that Java provides something like a function closure. The inner class exists within a context of an outer class with fields and methods. The inner object is constructed so that it has access to this outer context. In other words, the inner object has a closure that includes this outer context.

This can get even more tricky when you use what are known as anonymous inner classes that can appear in the middle of a method body, as in:

        public static void doSomething() {
            int x = 3;
            addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    // some code to execute
                }
            });
        }
This kind of code is used often in Java to implement what are known as listener objects that contain code to handle user interface events. Such objects usually need access to the context in which they are defined to carry out an appropriate action, so it is convenient that Java provides something like a closure. But it isn't a complete solution. For example, in the code above, the inner method could not refer to the local variable x. It would be fairly difficult to allow it to refer to x because local variables are allocated on the stack and the method called doSomething won't even be active when this object might be called on to execute its code. In Java, you can refer to local variables only if they have been declared to be final.

I don't want to take the time to explain all of the details of inner classes. The point is that this is a case where functional languages like ML have influenced languages like Java. In fact, I witnessed many interesting discussions about adding something like closures to C#. In the end, Microsoft decided to add something more powerful than Java's inner classes. They are called anonymous methods and they allow you to refer to local variables as well as fields and methods of the outer object, which means that it represents a true closure (a complete context). If you read about it, you'll find that they had to do some fancy dancing behind the scenes to make it work (e.g., introducing the idea that local variables are "captured" by anonymous methods, which requires them to be kept around for a longer period of time than a simple local variable).

I provided the following examples to the TAs to cover in section if there was time. It shows a practical use of an anonymous inner class to give some behavior to a button:

// Short example of a program with a frame that has a button that changes
// the color of the panel that contains it.  The ActionListener for the
// button is defined using an anonymous inner class that accesses fields of
// the outer object (colors, currentColor) and that calls a method of the
// outer object (repaint).

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class SimpleFrame extends JFrame {
    private JFrame frame;
    private JPanel panel;
    private Color[] colors = {Color.RED, Color.BLUE, Color.GREEN, Color.CYAN};

    private int currentColor;

    public SimpleFrame() {
        setTitle("Simple Frame");
        setSize(new Dimension(200, 200));
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        panel = new JPanel();
        panel.setBackground(colors[currentColor]);
        JButton b = new JButton("color");
        panel.add(b);
        add(panel);
        b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                currentColor = (currentColor + 1) % colors.length;
                panel.setBackground(colors[currentColor]);
                repaint();
            }
        });
    }

    public static void main(String[] args) {
        SimpleFrame f = new SimpleFrame();
        f.setVisible(true);
    }
}

Stuart Reges
Last modified: Sat Feb 3 19:01:48 PST 2007