Check out the new USENIX Web site.



next up previous
Next: Distributed Constraints Up: Lessons from the Neighborhood Previous: The Neighborhood Viewer

Programming with Constraints

Imagine a neuroscientist browsing brain data. Multiple 2D views of data are displayed; each is a projection centered on the same point in the brain. As the neuroscientist navigates in any of the views, the other views should update their images to remain consistent, i.e., centered on the same point. No matter how navigation is done - by dragging the image, scrolling, or directly entering new center coordinates, all views must remain consistent. Imagine also that the neuroscientist wants to display in the horizontal view two additional images that are 10 microns above and below the focal image. In our implementation of such a multi-view browser we would like to avoid two problems: having each view explicitly update all other ones, and remember which views are added and deleted.

The constraint-based programming model offers a good solution. This model allows the programmer to specify relationships between variables explicitly, independent of the control flow. The constraint engine enforces these relationships at runtime by updating dependent variables whenever related source variables change. These relationships are called formulas or constraints.

Our original implementation did not use constraints. Scrollbars were used to pan in x and y directions, and a scale allowed users to select another image from the stack. When moved, a scrollbar and scale widget sent the changed center coordinates to all scrollbars and scales in other views. We found our code hard to read and modify. We therefore chose to use TclProp [5] to re-implement the neighborhood viewer. With constraints, we identified a global 3D position and encapsulated each view's state in the x, y and z coordinates of its center. Since each view is the projection of the 3D space, we found it intuitive to express the x, y and z coordinates of its center as transformations of the global 3D position. The differences in data communication and Tcl code were significant and are illustrated in Figure 2.

 

 


: Two implementations of the neighborhood viewer. In the original implementation, each control widget must be aware of all other ones. In the implementation with TclProp, each control widget is linked via a constraint to a x, y, or z coordinate of the view center. The latter are in turn linked to the global position. Both links are two-way. Adding new views with TclProp requires only establishing connections between the center coordinates of the new view, and the position variable. Without TclProp, adding new views requires updating the code for each control widget of the other views.

The encapsulation of the view coordinates gave us the two important benefits. When the neuroscientist navigates in a view, for example, by moving a scrollbar, the scrollbar just sets the coordinates of the center appropriately. The formulas ensure that global position, and variables storing center coordinates of other views, are updated. When a view's coordinate variables change as a result of formula propagation, the constraints linking them to control widgets are triggered, and update the image in the view. For example, constraints on x and y of the view center scroll the view window in the x or y direction, respectively. The constraint on z loads a new image corresponding to the new value of z. The code became a lot simpler, since coordinate transformations were encapsulated in formulas between local and global coordinate variables, and did not have to be specified for each scrollbar or scale widget.

Second, with constraints, widget callbacks did not have to know about widgets in other views. Scrollbars and scales communicate via coordinate variables related by formulas. We could now add a view from a new direction without going through the program and adding move-notification code to each scrollbar or scale callback. The code became easier to modify. Views were originally separate modules for each projection plane, with a large
amount of code overlap. Now we have merged them into a single view module, customized by declaring appropriate formulas linking local and global coordinate variables. Altogether, by adopting the constraints model we reduced the number of lines of code by about a factor of two, and made the code a lot more understandable and extensible.

More generally, we learned two important lessons about constraints:

Constraints make code simpler. If a variable is updated in several places in the program, we only need to specify once what happens after the update. During the development of the neighborhood viewer, the neuroscientists asked to implement saving position bookmarks. When the neuroscientist goes to a saved bookmark, all views should center on the point with the global coordinates stored in the bookmark. If we use constraints to relate the views' centers to a global position, we just need to implement the bookmark interface and set the global position when a bookmark is selected; the constraint engine takes care of notifying the views of the new position. This localization of the update propagation code inside constraints is what makes the code simpler and more readable.

Constraints make code easier to modify and extend. Consider the task of adding a view from another stack of images. In the traditional model, we would have to add code to each existing view that notifies the new view of the change in position. With declarative programming, we create formulas that relate the view's center coordinates to the global position coordinates. Views are unaware of one another's identity, communicating only through the constrained coordinate variables. Constraints thus allow us to focus on the part of the application being developed, while relying on the constraint engine to maintain consistency between variables.

The declarative programming model also creates new challenges to the programmer. In Tcl, constraints can be declared and executed in different contexts, so care must be taken about variable substitutions. We sometimes found it difficult to determine the program state during execution, since we had no direct control over when and how variable updates were propagated. We added extensions to TclProp to dynamically disable and enable constraints using several criteria; these extensions were useful for determining the state of the running program. We think there is a need for a ``constraint visualizer'' to help programmers evaluate and modify execution of a constraint-based program.

In sum, constraints are generally useful where relationships between program variables can be expressed independently of the program flow, and
where there are multiple sources of variable updates. The declarative model is especially appropriate for event-driven programs, which do not have a predefined execution path, and usually have multiple ways to manipulate the program variables. The benefits of constraints are code reduction and improved extensibility, due to localized update propagation and hiding the information about dependent variables inside the constraints. Networks of constraints can be easily changed at development or runtime. We believe that the benefits of the declarative model need to be further explored by extending constraint packages, investigating domain areas that yield themselves to declarative programming, and writing more constraint-based applications.



next up previous
Next: Distributed Constraints Up: Lessons from the Neighborhood Previous: The Neighborhood Viewer



Alex Safonov
Mon May 27 13:14:56 CDT 1996