24. Process Oriented Simulation, take 1.5

Part of CS:2820 Object Oriented Software Development Notes, Fall 2015
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

 

Limitations of Java

In Java, when we tried to write code like this, it objected:

        public void exit( Vehicle v, float t ){
                /** Simulate v exiting this intersection at time t.
                 */
                System.out.println( "Exiting " + this.toString()
                        + " at time = " + t
                );
    
                Road d = v.pickRoad( outgoing ); 
                if ( queue.isEmpty() ) {
                        occupied = false;
                } else {
                        Vehicle v1 = queue.removeFirst();
                        new v1.ExitIntersectionEvent( t + travelTime );
                }
        } 

This code for exiting an intersection is incomplete, but after vehicle v has cleared the intersection, the code checks to see if there are any other vehicles waiting at the intersection. If there is one, it is dequeued and then the new vehicle is scheduled to exit this intersection.

In this vehicle-centered view of the world, all scheduling is done by the vehicle itself, and exit intersection events are a public inner class within class Vehicle. The initializer for this class, ExitIntersection(t) takes the time at which the event should be scheduled. Furthermore, this initializer actually does the scheduling, so we can discard the value returned by the initializer.

What's wrong with this?

Java does not allow us to call new v.ExitIntersection(). It does allow new Vehicle.ExitIntersection(), but we want to call new v.ExitIntersection() in a way that is aware of the class instance of Vehicle that it is working on.

We can, of course, kluge around this, and we will do that later, but at this point, it is worth taking the time to look at what Java does and does not support here, and how this differs from what it could support.

Nested Scope Rules

Java allows nesting. So for example, the body of a class definition is a list of declarations of methods, class components, or inner classes. Similarly, within the body of any method, you may declare local variables and inner classes.

In a language offering full support for nested scope rules, the rules for what you can declare at any level of nesting are identical. That is, wherever it is legal to place a class definition, you can also declare a variable or a method. The outermost block of the program is where global variables are declared (Java does not allow this). Methods declared in the outermost block are also global (you can consider the outermost block to be a single instance of an anonymous class called main, but Java does not allow this).

These uniform rules make it much easier to learn the language, but they pose problems for the language implementation.

It is worth noting that nested scope rules and objects can actually solve many of the same problems. One of the classic problems in information security is the mutual distrust problem:

The Mutual Distrust Problem

Consider 2 users, a and b, where neither user trusts the other. That is, a has resources, a.private that b must not be able to access, and b has resources, b.private that a must not be able to access. This would not be a problem if a and b had nothing in common, but a and b are each offering services to each other. That is, a has a service, a.public() that b must be able to call, and b has a service, b.public(), that a must be able to call. Furthermore, a.public() makes selective use of a.private and b.public() makes selective use of b.private.

The mutual distrust problem is a general problem in information security. You already know, at this point, how this problem is solved by object-oriented programming languages. The same problem shows up not only in programming languages, but also in file system design, particularly in the file systems for shared servers.

Block structured programming languages can solve this problem with the combination of block-structured scope rules and the ability to pass functions as parameters:

function a( function x, args) {
    private stuff

    makes unlimited use of a's private stuff
    calls x to do whatever the caller of a permits.
}

function b() {
    private stuff
    function c() {
        makes selective use of b's private stuff
    }

    call a( c ), passing function c
}

main program calls b

This does not have the symmetry of the object-oriented approach using public and private fields and methods of objects, but it does meet the minimum requirements for solving the mutual distrust problem.

The Problem of History

The C programming language does not permit generalized nested scope rules. Variables may be declared at any nesting level, but all functions are declared at the outer level. C functions are like Java static methods, but not enclosed inside a block or class declaration, so they only have access to global variables and whatever is passed as parameters.

C++, building on C, and Java, building on C++, began with no generalized nesting. Inner classes are an afterthought that was added to Java late in its evolution. As such, inner classes are not fully generalized examples of nested scope rules because they only support behavior that was easy to implement and compatible with the existing Java language.

Curiously, Simula '67, the original object-oriented language, included fully general nested scope rules. C++ was designed by Bjarne Stroustrup, an experienced Simula '67 programmer, but he only included in C++ those parts of Simula '67 that could easily be implemented by a preprocessor that generated C code as output when given C++ code as input. As a result, Simula '67 remains, in many ways, a more interesting language than C++. Unfortunately, Simula is not widely studied today, although Gnu Csim is a free implementation of most of Simula 67 that should work on just about any Unix-like operating system.