CSE341 Notes for Friday, 11/6/09

I began by finishing up a topic about static properties versus dynamic properties of program execution. 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
                                  flow of control
I said that I wanted to briefly mention polymorphism, which is a dynamic property. We considered a class defined as follows:

	public class Foo() {
	    public void method();
	        System.out.println("howdy");
	    }
	}
And I asked people to consider client code like this:

	Foo x;
	...
	x.method();  // always prints "howdy"?
On the surface, it appears that this will always print "howdy", but that's not necessarily the case. There could be a subclass of Foo that overrides method, which could change the behavior:

        public class Bar extends Foo {
            public void method() {
        	System.out.println("I'm trying to fool you");
            }
        }
I asked people whether we can figure this out at compile-time. Couldn't we look at the code and figure out what kind of object the variable x will refer to?

The answer is no. For example, the program might prompt the user and ask the user to pick which kind of object x should refer to. It could be even worse. The line of code above could be inside a loop that sets x to refer to different kinds of objects on different iterations of the loop. So the exact same line of code can end up calling many different methods.

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 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.

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.

So we can add "polymorphism, dynamic binding" to our list of dynamic properties of a program. 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.

Then I spent a few minutes pointing out that to understand Scheme, it is helpful to understand the motivation of the people who designed it. Some people just want to be consumers of programming languages. Others are interested in understanding the details of how programming languages are implemented. The designers of Scheme are very interested in language implementation. On the MIT Scheme page the first sentence about the language talks about its simplicity:

It was designed to have an exceptionally clear and simple semantics and few different ways to form expressions.
but the second sentence emphasizes the ability to implement programming language constructs in Scheme:
A wide variety of programming paradigms, including imperative, functional, and message passing styles, find convenient expression in Scheme.
Structure and Interpretation of Computer Programs includes an extensive discussion of how to implement a metacircular interpreter in Scheme. This is a Scheme interpreter written in Scheme. That may sound like a useless thing to have ("I already have Scheme, so why build another version on top of it?"), but it can prove quite useful. Probably the most useful byproduct of writing a metacircular interpreter is that you learn a lot about how Scheme itself works. It also gives you the ability to create your own variations to the language. Programming languages like Java are not very flexible. You can't, for example, add new control structures to the language. That's not true in Scheme. The language gives you a lot of room to create your own language constructs. And with a metacircular interpreter, you can create slight variations of Scheme that behave just the way you want them to. This is one explanation for the fact that there are so many different versions (often called "flavors") of Scheme.

Then we discussed equality operators in Scheme. In ML the = operator was used to compare equality of many types of data, including structured data. In Scheme the = operator is used for numerical equality. So if you know that the two values you are comparing are numbers, then you should use the = operator.

If you aren't guaranteed to be comparing numbers, then you have a range of equality operators to choose from. The strictest of these is the eq? predicate. It does a pointer comparison (are these references to the exact same object?). This is similar to Java's == operator for objects. This can have surprising results for numbers. For example, we got the expected result when comparing integer values:

        > (define a 13)
        > (define b 13)
        > (eq? a b)
        #t
but not when comparing floating point numbers:

        > (define c 13.8)
        > (define d 13.8)
        > (eq? c d)
        #f
Similar issues come up in Java as well. If you execute this code:

	Integer a = 13;
        Integer b = 13;
        System.out.println(a == b);
        Double c = 12.4;
        Double d = 12.4;
	System.out.println(c == d);
	Integer e = 666;
	Integer f = 666;
	System.out.println(e == f);
The output is:

        true
        false
        false
In Java, the floating point numbers are always stored as distinct objects, so when you ask whether they are "==" to each other (whether they are exactly the same object), the answer is no. In the case of integer values, Java sometimes uses separate objects and sometimes uses a cached version of the object. The language standard says that only a certain range of integes will be cached, which is why we get different results using 13 versus 666.

This issue comes up with lists as well. Scheme will, in general, try to avoid making copies when it doesn't have to. For example, given these definitions:

        > (define x '(1 2 3))
        > (define y x)
        > (define z '(1 2 3))
Scheme will create two lists objects. The first list is pointed to by both x and y. The second is pointed to by z. So when we compare with the eq? predicate, we find that x and y are equal (are the same pointer) but not x and z:

        > (eq? x y)
        #t
        > (eq? x z)
        #f
The eqv? predicate is slightly stronger than the eq? predicate. It returns true in the same cases but also returns true for simple values like numbers. So with eqv, we see that the floating point comparisons now return true, but the list comparisons still do not:

        > (define a 38.4)
        > (define b 38.4)
        > (eqv? a b)
        #t
        > (define x '(1 2 3))
        > (define y x)
        > (define z '(1 2 3))
        > (eqv? x y)
        #t
        > (eqv? x z)
        #f
The third variation is the equal? predicate, which can be thought of as a deep equality operator. It recursively compares the values in each structure. So with the equal? predicate, even our list example works:

        > (equal? x y)
        #t
        > (equal? x z)
        #t

Stuart Reges
Last modified: Sat Nov 7 14:17:41 PST 2009