Evaluating Predicate-Oriented Programming:
Using Predicate Fields in a Highly Flexible Industrial Control System
Shay Artzi & Michael D. Ernst
Introduction
At the heart of object-oriented programming is the ability for run-time
values to affect program behavior. Dynamic dispatch selects which implementation
of a method to invoke, based on the run-time class of the receiver object.
Researchers have proposed other, more powerful ways for run-time values
to affect both the behavior and the structure of objects.
Predicate classes [1] use predicates to dynamically classify
an object to its subclasses. Predicate fields give a way of affecting
object structure rather than object behavior. A predicate field is a field
of an object that is present (can be read and written) depending on the
values of other fields. In other words, the structure of an object varies
at run time depending on the values of its own fields.
We present a case study of two command and control systems for a facility
in which physical experiments are performed. The second system was designed
with requirements similar to the first, and uses predicate fields in order
to mitigate deficiencies in the first implementation. This use of predicate
fields was successful: the resulting system is very flexible, the user
interface is highly configurable, and development costs, especially in
the user interface, were greatly reduced.
We believe this is the first evaluation of predicate-oriented computing
in a realistic, large-scale development effort. Our observations help
to indicate circumstances in which predicate fields are most useful, and
the practical obstacles to their use. Our observations also suggest that
many important benefits are obtained even in the absence of sophisticated
predicate expressions and dispatching mechanisms. One implication for
practitioners is that it can be advantageous to use even a primitive implementation
of this language feature.
Predicate Fields
A predicate field is a field whose presence in an object depends on
the run-time value of a predicate (a boolean expression). Predicate fields
allow an object's structure to vary at run time.
Four motivations for predicate fields are as follows. First, predicate
fields allow an object to change its structure during its life cycle (e.g.,
an employee gets promoted from hourly to yearly salary). Second, predicate
fields permit the user to recover from errors, such as changing the type
of a created object while preserving all the data which is mutual to both
types. Third, predicate fields can ease UI development by presenting a
unified view on arbitrarily many collections of fields (for example, using
reflection). Fourth, predicate fields permit fine-grained customization
of objects or object behavior, without requiring new class definitions
(tagging an employee with special fields).
Predicate Fields Library
We have implemented predicate fields as a library in C#. Previous predicate-oriented
systems [3,4,1,6,2,5]
are based on language extensions, some of which are accompanied by a prototype
implementation. Our decision to use a library is a pragmatic one.
Our library supports the creation of <em>dynamic objects</em>,
objects in which each field is a predicate field. A dynamic object is
an instance of a dynamic type. Conceptually, the declaration
of a dynamic type lists the (predicate) fields contained in each object
of the type.
Each dynamic object has a type, called its dynamic type; the
dynamic types partition the set of all dynamic objects. The dynamic type
is stored in a dynamic object upon creation and is permitted to change
subsequently. A field definition contains its type, predicate, user interface
information and persist storage. Field types specify the type of the data
that is stored in the field, a class used to edit its value, and a class
used to convert its value to and from a printed representation. A predicate
is constructed from a pair of a field name and a value of the field's
type. This decision implies that developers can define a predicate on
every possible value of the object's state.
Case Study: Experimental Control System
We performed a case study involving two systems that control facilities
for complex experiments. The two implementations serve as command and
control software for experiment-conducting facilities. Their goal is to
enable scientists and technicians to define, control, execute, and examine
these experiments.
An experiment consists of a set of devices and a sequence of operations
of those devices. Examples of devices are thermometers and drill presses.
Examples of operations are measuring the temperature and drilling a hole.
The operations of an experiment are written in a language called MML,
or Mission Modeling Language.
The user interface is tree-based. The experiment designer adds new MML
statements to the statements tree and new devices to the devices tree.
Each element in the tree can be configured by a related properties form.
The operator can observe the properties of the various items in the trees
as well as the progress of the experiment shown on the statement tree.
The key requirement for the experimental control system is adaptability
to physical hardware changes and replacements. New devices are developed
constantly; incorporating those devices into the system should require
no software changes in the experimental control system (but may require
changes to experiments). In order to address these requirements, the design
includes a highly configurable two-level system architecture, a knowledge
level and an operational level. The operational level describes the concrete
model of the system, derived from the functional requirements. The knowledge
level contains the meta-model of the system and defines the legal configurations
of operational level objects.
Implementations
We built two implementations of the experimental control system. The
first implementation is in daily use at one experimental facility. The
second implementation is in incremental integration at a different facility
and will eventually replace the first implementation. Re-implementation
took advantage of knowledge gained from the first effort and permitted
exploration of different design decisions, most notably the use of predicate
fields.
Implementation 1 is considered highly successful. It has been used daily
since 2001 to design and run many experiments. It transformed the way
that work was performed at the specific facility where it is installed,
and other facilities are eager to obtain it.
There are three design deficiencies in implementation 1 that led to the
decision to use predicate fields in Implementation 2. First, each object
in the user interface (statement, device, device type, group, command,
etc) had a custom-made property page for editing its information. User
interface development took a significant share of the development time,
and it was difficult to integrate new devices with unfamiliar editing
information. Second, changes to the structure of the statements required
cross cutting layer changes. Third, the type of an object could not be
changed. Suppose the experiment designer added an automatic statement,
then wanted to change it to a manual statement. He would have to delete
the automatic statement and create a new manual statement in its place
(losing all the mutual information).
Predicate Fields Motivation
This section gives three motivations for the use of predicate fields
in Implementation 2.
First, we realized that all objects share the same characteristics while
being edited. Implementation 1 connected the user interface tree's nodes
(statement, device, ...) with viewers of the appropriate objects. However, it caused
tight coupling between the objects in the user interface layer and the
objects in the business logic layer resulting in the cross cutting modification
problem mentioned earlier. The information required when editing an object
consists of four parts: the object data structure, the possible values
for each field in this structure, the storage location of each field,
and the connection between the different fields. This led us to design
a dynamic object, whose structure is defined by predicates on its state.
The fields carry all the information that is used to edit, save, and load
them. Every object that is selected has the same property page created
using a PropertyGrid gadget (the PropertyGrid gadget queries the object
about its fields using reflection and presents them in a readable way
to the user.).
Second, the level of uncertainty in which the development team had to
work pushed us into generalizing beyond Implementation 1, which was itself
highly flexible. Implementation 1's knowledge level included devices,
devices types, and commands. Using predicates to emulate dynamic reclassification
into subclasses allowed us to move the statement hierarchy from the operational
level into the knowledge level. Predicate fields also enabled us to insert
device types' user interface information into the knowledge level. Moreover,
controlling the entire knowledge level with predicates allows finer control
over the operational level. Finally, by having each field contain all
the knowledge needed to edit, load and save it the users can easily support
new devices which are combinations of the existing fields.
Third, using dynamic classification permitted us to improve the user
interface by allowing the designer to change the type of edited objects
without removing the old one and creating the new one. The changed objects
retain all their mutual information, saving the experiment designer the
trouble of supplying the same information repeatedly.
Experience
User interface developers had to interact with the dynamic objects. A dynamic
object provides a simple interface to query the values of its fields. In
addition, it allows objects to register to a variety of events that are
activated when the corresponding field's value is changed. The developer only
had to instantiate the object (with the appropriate dynamic type), and
possibly choose a storage location.
Finally, the developer can pass the dynamic object to a wrapper object that can
be used for reflection (for example passing it to PropertyGrid gadget for
editing). Those developers reported that due to its simple interface the
dynamic objects were easily used and the result was a significant decrease in
development time. Some of the developers were even surprised by the ease and
quickness of the development process.
Developers adding support for new requirements and features had to modify
the dynamic types (rarely), fields, field types, and predicates (more
frequently). These modifications demanded understanding the interaction
between the different data structures. Adding new predicates and fields
and modifying existing ones was initially difficult for most of the developers.
This initial difficulty can be attributed to the following four factors,
which we consider to be real limitations of the system. However, most
problems result from the library syntax and would have had less impact
had predicate fields been implemented as a language extension.
- Using a declarative approach. Parts of the resulting software can
be harder to understand. Since we are using declarative definitions,
it is very hard to grasp a component's full structure (even if we did
not take into account dynamic structure modifications).
- Far-reaching effects of modification to the predicate data. Since
system behavior heavily depends on meta data, changes to the meta data
can have far-reaching affects. It was often the case that a careless
developer made a seemingly simple change to the predicates fields definitions,
only to discover that the system had become unusable. This requires
careful modification, well-documented knowledge level structure, and
unit testing.
- Predicate fields are implemented as a library, rather than as a language
extension. This can cause the software to be less readable (again due
to the declarative approach) and incur some performance overhead when
modifying the object structure and accessing fields within the object
(this overhead is negligible since dynamic objects are used in the user
interface).
- Type safety problems. Objects appear to change their type dynamically.
Combined with the library implementation, static type checking is very
hard. However, developers can use testing scheme to easily transform
their beliefs about the correlation between the object's state and its
set of fields, into tests.
Whereas many developers initially disliked the predicate fields library,
all developers reported that once they became familiar with defining predicates
and fields (usually after the first two or three), they were able to proceed
with ease. They also found out that their perspective toward designing
the user interface has changed and noted that one result of using the
predicate fields was that almost no custom forms had to be built.
Conclusions
We have presented an implementation of predicate fields as a library,
and a case study of the use of predicate fields in a substantial industrial
control system. Developers found predicate fields useful in practice.
The library is used successfully and intensively in a project with very
tight deadlines. This case study provides concrete evidence that other
practitioners should try predicate fields, and that researchers should
continue to refine designs and implementations.
Developers used predicate fields to support greater software flexibility,
both for themselves and for power users. Predicate fields gave developers
greater control over objects, allowed fined-grained modifications to specific
fields, and provided a declarative approach to defining object structure,
permitting changes to be easily reflected throughout the system.
Predicate fields were particularly useful in providing a uniform interface
to an arbitrary (and dynamically changing) number of possible object structures,
which was achieved through a combination of reflection and treating dynamic
objects (created by the library) as real objects. Our experience suggests
that predicate fields may be particularly useful in projects with a high
level of user interface requirements. Furthermore, predicate fields are
a good match for projects in which requirements (and persistent objects)
change frequently, because they permit both the code and the objects themselves
to be easily adapted to new circumstances.
The predicate fields implementation has a number of limitations: it
is implemented as a library rather than integrated with the programming
language; it does not support predicate dispatching or predicate classes;
the syntax for predicates is very limited; and it uses dynamic type-checking,
giving the possibility of run-time type errors. Despite these limitations,
the implementation was sufficiently powerful to solve the problems encountered
in building the industrial control system, and the developers did not
feel that (for example) lack of support for dynamic behavior modification
of objects was a significant hindrance. This result is suggestive regarding
how much linguistic power and complexity is desirable in practice.
The case study also revealed some potential downsides of using predicate
fields, some of which are consequences of the implementation and some
of which are inherent to the use of predicate fields. Language and tools
support are important in reaping the potential benefits of predicate fields.
The declarative nature of the predicates was useful in propagating changes
throughout the system, but the same characteristic (non-local control)
could make component behavior difficult to understand. The biggest remaining
problem is lack of static type safety. In the absence of integration into
a widely accepted programming language implementation (a distant prospect),
we have proposed a testing mechanism that may mitigate this problem.
Research Support
This research is supported in part by NSF grant CCR-0133580.
References
[1] C. Chambers. Predicate classes. In ECOOP '93,
the 7th European Conference on Object-Oriented Programming, pp. 268--296,
Kaiserslautern, Germany, July 1993.
[2] M. D. Ernst, C. S. Kaplan, and C. Chambers. Predicate
dispatching: A unified theory of dispatch. In ECOOP '98, the 12th
European Conference on Object-Oriented Programming, pp. 186--211,
Brussels, Belgium, July 1998.
[3] J. Hamer, J. Hosking, and W. Mugridge. A method for
integrating classification within an object-oriented environment. Technical
Report Auckland Computer Science Report No. 48, Department of Computer
Science, University of Auckland, Oct 1990.
[4] J. Hosking, J. Hamer, and W. Mugridge. Integrating
functional and object-oriented programming. In Technology of Object-Oriented
Languages and Systems TOOLS 3, pp. 345--355, Sydney, 1990.
[5] T. Millstein. Practical predicate dispatch. In Object-Oriented
Programming Systems, Languages, and Applications (OOPSLA 2004), pp.
345--364, Vancouver, BC, Canada, Oct 2004.
[6] A. Taivalsaari. Object-oriented programming with
modes. In Journal of Object-Oriented Programming, pp 25--32,
June 1993. |