The Ptolemy Programming Guide

Ptolemy's Goals

Ptolemy's goals are to improve a software engineer's ability to separate conceptual concerns. These goals are similar to that of implicit invocation design style and aspect-oriented languages. In addition to these goals Ptolemy aims to separate crosscutting concerns in a manner that preserves the encapsulation of the object-oriented code and allows programmers to reason about object-oriented code and crosscutting code modularly.

Running Example

The code for this example is available from the Ptolemy compiler distribution in the examples directory. The reader is encouraged to consult our pages on installing and running Ptolemy compiler for instructions on how to obtain the full example source code.

As an example, consider a drawing editor which consists of objects such as Points, Lines, etc. Each element is part of the overall figure and implements the FigureElement interface (for figure elements). Other classes may be interested in knowing when such figure elements change. For example, there may be an Display class that updates the figure on the screen when any element changes. The interface FElement is shown in the listing below.

public interface FigureElement {
	void draw();
}

The class Point is an example of an FigureElement. Points have an x and a y location and setter methods setX and setY. Points also have a method makeEqual, which will set the x and y location of its argument to that of the current instance.

All three methods modify an instance of class Point and thus they all call the Update method of the class Display to refresh. The first two methods ask the Display to redraw the current instance while the makeEqual method (since it modifies the other instance) asks the Display to redraw the other instance.

public class Point implements FigureElement {
	int x;
	int y;
	public void setX(int x) {
		this.x = x;
		Display.Update(this);
	}
	public void setY(int y) {
		this.y = y;
		Display.Update(this);
	}
	public void makeEqual(Point other) {
		other.x = this.x;
		other.y = this.y;
		Display.Update(other);
	}
}
                 

One can see that all figure elements like Points, Lines, Circles, etc will need to call the Update method of the Display class whenever there is any change in the figure elements. For example, this code is present in methods setX, setY, and makeEqual of the class Point. There are two main disadvantages of such implementation.

  • First, it couples classes Point, Lines, Circles, etc with the class Display. As a result, it becomes harder to reuse these classes without also having the class Display in that program. Not all programs containing Points, Lines, Circles are necessarily required to have displays, however.
  • Second, it is not easy to update such code to reuse the notifications to the Display class. For example, if a developer wishes to also log the changes to figure elements, they will have to either insert calls to the logger again in all of Points, Lines, Circles, etc or they can also insert code to log in the class Display. Both these situations are not satisfactory as the first requires "exposing the change in figure element again" and the second couples the class Display with the class Logger, which may not necessarily be related to each other.

Such requirements that cut across the object-oriented code are called crosscutting concerns. Ptolemy and other languages aim to modularize these concerns, but Ptolemy aims to do so such that it preserves encapsulation of object-oriented parts such as classes Point, Line, Circle, etc and allow us to understand and reason about object-oriented parts such as classes Point, Line, Circle and the crosscutting parts such as the class Update one class at a time. Being able to understand systems in that manner has distinct advantages.

Designing the Interface between Object-oriented and Crosscutting Code

To that end, first design and programming task in Ptolemy is to define the interface between the object-oriented code and the crosscutting code. This interface is expressed in the form of an event type declaration.

The design of an event type declaration requires careful considerations in a manner similar to the design of an application programming interface (API). In particular, two questions are important.

  • What are the important abstract events in my application?
  • What information must be available when these events occur?

For example, for our drawing editor example, the abstract events of interest are changes in figure elements. Furthermore, it would be important to be able to identify the specific instance of the figure element that is changing.

An Example Event Type Declaration (FEChanged)

Upon arriving at such conclusion about the drawing editor example, we can encode it in the form of the following event type declaration.

public void event FEChanged {
	FigureElement changedFE;
}

As seen above an event type is declared with the keyword event. An event type also has return type, here void, which allows crosscutting code to return values to object-oriented code. Finally, an event type declares zero or more context variables. These context variables represent information available when a concrete event of this type happens in the application. The event type FEChanged here declares one context variable changedFE of type FigureElement that represents the figure that is changing. The intention of this event type declaration is to provide a named abstraction for a set of events.

Quantification using Event Types and Bindings

Event type declarations are the key novel feature of Ptolemy. The name of an event type allows programs to refer to a set of events and thus to quantify over such a set of events. So for example, the name FEChanged refers to a set of events thus it could be thought of as for all events such that their type is FEChanged.

In Ptolemy, this can be used to write binding declarations. A binding declaration in Ptolemy associates a method in a class to a set of events identified by an event type. An example is shown below.

public class DisplayUpdate {
	...
	when FEChanged do update;
	public void update(FEChanged next) throws Throwable {
		...
	}
}

The binding declaration on line 3 says to run method update when events of type FEChanged are announced.

The use of the event type names in binding declarations simplifies these declarations and avoids coupling class DisplayUpdate to classes that may signal events of type FEChanged. This allows selecting a number of such event signalling with just one succinct binding declaration, without depending on the modules that announce events.

Activating All Bindings in a Class

Ptolemy allows bindings to be activated at runtime. This is done by using the register expression as shown in the following listing.

public class DisplayUpdate {	
	public DisplayUpdate() {
	 register(this);
	}

	when FEChanged do update;
	public void update(FEChanged next) throws Throwable {
		...
	}
}

An example of register expression appear on line 3 for the constructor method of the class DisplayUpdate. The effect of running this expression is to activate all binding declarations in that class.

Announcing an Event

Events are explicitly signalled in Ptolemy. Event signalling is declarative, explicit and typed.

Event signalling is declarative in the sense that it doesn't require specifying the mechanism used to signal it (unlike implicit-invocation and idioms for observer patterns). This allows Ptolemy compilers to optimize event signalling behind the scene, while maintaining the intended semantics.

It is typed in that signalling an event requires mentioning the name of an event type, which must be a valid event type declaration in the program. This allows Ptolemy compilers to check the correctness of signalling events and include such signals in the set of events for that event type.

The event signalling is done using the announce expressions. Several example usage of announce expressions for our figure editor examples appears in the listing below.

public class Point implements FigureElement {
	int x;
	int y;
	public void setX(int x) {
		announce FEChanged(this) {
		 this.x = x;
		}
	}
	public void setY(int y) {
		announce FEChanged(this) {
		 this.y = y;
		}
	}
	public void makeEqual(Point other) {
		announce FEChanged(other){
		 other.x = this.x;
		 other.y = this.y;
		}
	}
}

In the listing above the announce expressions occurs three times on lines 5-7, 10-12, and 15-18. At all these locations the event being signalled is of type FEChanged.

The method class like syntax of announce expressions allows binding local values at the site of event signalling to context variables of event type declaration. This binding is such that ith argument of the announce expression is bound to the ith context variable of the event type declaration. Here, line 6 binds this to context variable changedFE and line 15 binds other to context variable changedFE. Notice that the same context variable is receiving different bindings for different event signalling sites. However, the class DisplayUpdate will be able to access all of these uniformly using the name changedFE.

Overriding Features in Ptolemy

In Ptolemy, multiple handlers could register to handle the same event. These handlers execute in the order they are registered, but when it comes to their execution, the next applicable handler does not always run after the end of the execution of the preceding one. Invoke expressions can override the way handlers are executed. Basically a handler can transfer the control to the next applicable handler, or event body if there are no more applicable handlers, using the invoke expression. If the invoke expression is missing in one handler execution of all the other applicable handlers after it is skipped. An example is presented in the listing below.

public class DisplayUpdate {
 public DisplayUpdate() {
  register(this);
 }
				
 when FEChanged do update;
 public void update(FEChanged next) throws Throwable {
  System.out.println("Inside display update:Before Invoke");
  next.invoke();
  Display.update();
  System.out.println("Inside display update:After Invoke");
 }
}

public class LogChange {
 public LogChange() {
  register(this);
 }
				
 when FEChanged do log;
 public void log(FEChanged next) throws Throwable {
  System.out.println("Inside log:Before Invoke");
  next.invoke();
  Log.log(next.fe());
  System.out.println("Inside log:After Invoke");
 }
}

Suppose having two handlers LogChange and DisplayUpdate to log changes to the figure element and update the display such that LogChange is registered after DisplayUpdate. Upon event announcement and transfer of the control to DisplayUpdate, invoke expression on line 9, causes the control to be transferred to the LogChange handler and the invoke expression on line 23 passes the control to the event body. After the event body execution returns, the body of LogChange is executed and finally in the end the body of DisplayUpdate after the invoke expression on line 9 is executed. As it can be seen, invoke expression overrides the control such that, although the DisplayUpdate is the first registered handler, it does run last. Interestingly, if the invoke expression on line 9 goes missing, execution of the handler LogChange and the event body is skipped.

Page last modified on $Date: 2012/01/07 16:25:19 $