The Ptolemy Programming Guide
Table of Contents
- Programming Guide Overview
- Getting Started with Ptolemy
- Installing and Running the Ptolemy compiler
- The Ptolemy Programming Language
- Design-by-Contract Methodology in Ptolemy
- Technical Publications
Examples
Introduction to the Ptolemy Language
With traditional object-oriented techniques, when we implement some requirements (e.g. exception handling, synchronization, resource sharing and other resource management protocols, logging, etc) code for such requirements is scattered everywhere in the software and tangled with code for other requirements. Such requirements are called crosscutting concerns. As a result, when maintenance requests arise in these type of requirements, developers have to study a large number of modules in their software system to identify changes that are required to address maintenance request.
The aim of the Ptolemy language is to provide programming language mechanisms that enable software developers to implement these type of requirements (e.g. exception handling) in separate modules called handlers. This enables better separation of such concerns. In this sense, Ptolemy has the same goals as aspect-oriented languages like AspectJ. However, aspect-oriented languages also have certain limitations that Ptolemy doesn't have.
Ptolemy's language design incorporates several good ideas from existing languages, e.g. AspectJ, Eos, Rapide, and CaeserJ. The novel features of Ptolemy are found in its event model and type system. Ptolemy provides full support for declarative event-based programming.
Like Eos and unlike AspectJ, Ptolemy does not have special syntax for “aspects” or “advice”. Instead it has the capability to replace all events in a specified set with a call to a handlers.
Compatibility
Ptolemy is backwards compatible with object-oriented languages. For example, PtolemyJ that extends Java to add Ptolemy-specific features is compatible with existing Java programs. Every valid Java program is a valid PtolemyJ program. PtolemyJ compilers produce bytecode that runs on standard Java virtual machine.
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 insertcode 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.
An Event Type Declaration in the Ptolemy Language
Designing the Interface between Object-oriented and Crosscutting Code
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. An event type declaration in the code is marked with the keyword event.
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, three questions are important.
- What are the important abstract events in my application?
- When should such events occur 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. These events should occur in object-oriented code at places where the state of a figure elements is modified, for example at places in the class Point where a point's x or y coordinate changesor at places in the class Line where a line's end-points changes. 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.
/*** * This event is signaled when the state of a figure element changes. */ 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 type name FigureElement must be visible in the scope. The intention of this event type declaration is to provide a named abstraction for a set of events.
The type name of context variables must be visible in the scope of the event type declaration.
A top-level event type declaration can have the public visibility modifier or no modifier (in which case it has package level visibility).
A nested (inner) event type declaration can have one of private, protected, public or no modifiers.
Another example of an event type declaration can be seen below.
/*** * This event is signaled when a figure element moves. * When this event is signalled details of the movement along * the X and the Y co-ordinates is available. */ public FigureElement event FEMoved { FigureElement movedFE; int deltaX; int deltaY; }
The purpose of this event type declaration is to provide a named abstraction for a set of events that correspond to a moving figure element. It declares three context variables: movedFE, deltaX, and deltaY. It also declares a return type FigureElement. The return type of an event type is used to check the type of an event announcement and event handlers (described later).
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. An important benefit is that referring to elements in such a set of events does not require explicitly enumerating them. 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.
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. There is a corresponding expression counterpart, but for this discussion we will focus on 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) { this.x = x; announce FEChanged(this); } public void setY(int y) { this.y = y; announce FEChanged(this); } public void makeEqual(Point other) { other.x = this.x; other.y = this.y; announce FEChanged(other); } }
In the listing above the announce expression occurs three times on lines 6, 10, and 15. 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.
It is also possible to allow overriding of the original code by explicitly enclosing that code in an announce expression. In the listing below an example of such announce expression is shown.
public class Point implements FigureElement { ... public FigureElement moveBy(int deltaX, int deltaY) { return announce FEMoved (this, deltaX, deltaY) { this.x += deltaX; this.y += deltaY; return this; } } }
The announce expression in the listing above allows overriding of the code on lines 5-7 (for example to implement movement restriction as a separate concern). Notice that the type of the return value of the block enclosed by the announce expressions (here Point) is a subtype of the declared return type of event type declaration FEMoved (here FigureElement).
This form of announce expression is more general compared to the previously shown nonblock form announce FEChanged(this);. In fact, the nonblock form is implemented by translating announce FEChanged(this) to announce FEChanged(this) { return null; }. Thus, it is just a syntactic sugar provided for convenience.
A Handler in the Ptolemy Language
An object-oriented class is called a handler class (or simply handler) in Ptolemy's terminology if it reacts to events signalled by other classes in that software system. Handlers typically implement croscutting requirements.
Handlers are declared using the keyword class so like the Eos programming language there is no distinction at the declaration level between standard object-oriented classes and handlers.
An example of a handler declaration is shown below:
public class DisplayUpdate { ... when FEChanged do update; public void update(FEChanged next) throws Throwable { ... } }
This declaration has two parts: a standard object-oriented method update on lines 4-6 and a new construct in Ptolemy called a binding declaration on line 3.
Binding Declaration
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 above on line 3. In this binding declaration, the identifier FEChanged refers to the type of an event type declaration. Briefly, an event type declaration gives a type for events that can be signalled in that program. The identifier update refers to the name of a method.
... ... when FEChanged do update;
Effectively, 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.
Both event type name and method name used in a binding declaration must be visible in the scope of that binding declaration.
Activating All Bindings in a Class
Ptolemy allows bindings to be activated at runtime using register expressions. 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 { ... } }
An example of register expression appear on line 3 for the constructor method of the class DisplayUpdate. The effect of this expression is to activate all binding declarations in that class.
In the listing above the register expression on line 3 says that whenever an instance of class DisplayUpdate is constructed using the default constructor DisplayUpdate() it is activated such that whenever event FEChanged is signalled method update is run.
Invoke Expression and its Semantics
In Ptolemy, handlers take an event closure as their first argument. The event closure contains the code needed to run the applicable handlers and the original event body. The event closure is run by invoke expressions. Invoke expression runs the handlers by the order they are registered and if there are no handlers, the event body is run. Invoke expression, transfers the control to the next applicable handler. An example is presented in the listing below.
public class DisplayUpdate { ... @When(FEChanged.class) public void update(FEChanged next) throws Throwable { next.invoke(); Display.update(); } }
The handler update is supposed to update the display after a figure element is changed. Thus, it calls invoke on the closure parameter of the handler on line 5, before anything else, to transfer the control to the event body, making sure the event body is executed before the update. After event body executes, the control is transferred back to the the handler update which in turn updates the display on line 6. If lines 5 and 6 are replced with each other, then the display is updated first and then the event body is executed, which means the display is updated before the figure element is changed.
BNF Grammar for New features in Ptolemy
Ptolemy extends Java with new mechanisms for declaring events and for announcing these events. These syntax extensions are shown below. In this extension, productions of the form "..." represent all existing rules for Java plus rules on the right.
<TypeDecl > ::= ... | <EventDecl>
<EventDecl> ::= [ <Modifier>* ] <Type> event <Identifier> { <ContextVariable>* [ <Contract> ] }
<ContextVariable> ::= <Type> <Identifier> ;
<ClassBodyDecl> ::= ... | <BindingDecl>
<BindingDecl> ::= when <Type> do <Identifier> ;
<Expr> ::= ... | <RegisterExpr> | <InvokeExpr>
<RegisterExpr> ::= register ( <Expr> ) ;
<InvokeExpr> ::= <Expr> . invoke ( )
< Statement> ::= ... | <AnnounceStmt>
<AnnounceStmt> ::= announce <Type> ( <Expr>* ) { <Expr> }
<AnnounceStmt> ::= announce <Type> ( <Expr>* ) ;
Ptolemy also allows developers to write contracts as part of the event type definition. This design-by-contract methodology in Ptolemy is described in detail later. The grammar for writing contracts is shown below.
<Contract > ::= requires <Expr> assumes <AssumeBlock> ensures <Expr>
<AssumeBlock> ::= { < AssumeBlockStatement >* }
<AssumeBlockStatement ::= <Statement> | <SpecStatement>
<SpecStatement> ::= requires <Expr> ensures <Expr> ;
<SpecStatement> ::= preserves <Expr> ;
<SpecStatement> ::= establishes <Expr> ;
The expressions expr in "requires expr", "ensures expr" "preserves expr", and "establishes expr" must be pure, i.e. evaluation of this expression shall not produce any side-effect such as writing a program variable.
Page last modified on $Date: 2012/01/07 16:25:19 $