next up previous
Next: Status of Implementation Up: No Title Previous: The Box Layout


Meta-Level Techniques for Separating Application and Visualization

 

In order to ensure clear program structures, application and visualization objects should be separated. The second part of this paper discusses how to use the meta-object protocol of CLOS (see also elsewhere [15,16]) for linking application and visualization layers. This link is mostly based on interesting events generated by application objects. These events have to be visualized in one way or another. The recognition of interesting events is a well-known problem since these events are very often only indirectly reflected by algorithms. We refer to Brown [17] for a detailed discussion of these problems.

Our approach associates visualization objects with given application objects without requiring any source code modifications to the application. We support multiple views as well as controllers for manipulating the application's data structures. Several other mechanisms have also been developed (Model-View-Controller-Scheme [18], CLUE [19], Presentation-Types [20,21]).

As example application we chose a simple constraint net. There is no need to present the application code since everything can be found in detail elsewhere [22]. The application provides a simple model of a stock exchange scenario. When are some stocks to split? The participants have uncertain knowledge and are influenced by one another. A constraint net models these influences by propagating certainty-estimation intervals between 0 and 1. This interval of a `broker' might be visualized by a gauge [22]. The implementation distinguishes assertion objects (brokers, mystics, virtual intermediates, etc.) and constraint objects (or, and). Figure 9 shows a snapshot of a program animation. The example configuration consists of gauges for assertions and simple nodes for constraints.

  
Figure 9: The upper and lower bounds are indicated by shaded rectangles [22]. The gauges show the estimation interval from 0 (bottom) to 1 (top).

The visualization in Figure 9 can be built with the techniques described in the previous sections. The graph is defined by a set of participants and a successor function stock-exchange-wizard.

(defmethod stock-exchange-wizard ((participant assertion))
  "Wizard's information about connections of assertion objects."
  (assertion-constraints participant)) ; OR nodes

(defmethod stock-exchange-wizard ((participant constraint))
  "Wizard's information about connections of constraint objects."
  (list (constraint-output participant))) ; brokers, mystics, etc.

Another function, application-visualization-coupler, is used to define a mapping from application to visualization objects (see Section 5.2). Both functions are generic, i.e. different mappings may be specified for different classes of application objects.

Indirect Values for Visualization Objects

Visualization objects have to refer to objects of the application side. There should exist a ``dynamic'' binding which could be easily maintained provided that classes of visualization objects offer support for some kind of active values [23]. We present a simplified CLOS metaclass supporting non-nested active values which we call indirect values. Indirect values are defined by the form (make-indirect-object object reader writer) where is optional. The following definitions sketch an implementation using a new metaclass and a corresponding meta-level method for the generic slot accessor function slot-value-using-class. Writing to slots with indirect values can be implemented analogously.

(defclass indirect-slots-class (standard-class)
  ()
  (:documentation "Metaclass supporting indirect slot values."))

(defmethod check-super-metaclass-compatibility ((x indirect-slots-class)
                                                (y standard-class))
  t) ; We do not care about that in this paper (see Graube [24])

(defmethod slot-value-using-class ((class indirect-slots-class) 
                                   object slot-descriptor)
  (let ((direct-slot-value (call-next-method)))     ; get slot value
    (if (indirectp direct-slot-value)               ; test for indirect value
      (funcall (indirect-reader direct-slot-value)  ; notify application object
               (indirect-object direct-slot-value)) 
      direct-slot-value)))

Visualization objects may have indirect-slots-class as their metaclass. With indirect slot values every slot access is delegated to the corresponding application object if required. Using the meta-object protocol it would be easy to determine all indirect objects or that indirect object referred to by a specific slot. The gauges for the stock exchange example use this metaclass to refer to the exchange participants. But what about the other direction: the gauges have to be ``informed'' when the participants' estimations of stock splits change.

Slot Demons for Application Objects

 

An assertion object has one slot for the lower and one for the upper bound estimation. The corresponding visualization objects have to be informed when either of these slot values change. The most obvious way to achieve this is to define the assertion class with a metaclass that allows demon functions to be attached to slots. The ``real'' value of a slot is a structure that provides a value facet and an -modified facet [25]. All slot demon functions are evaluated when the slot value changes. The implementation of slot demons is similar to the one of indirect slot values. We introduce a metaclass demon-slots-class and define modified versions of slot-value-using-class and (setf slot-value-using-class), which access the value facet. The latter one evaluates the demons in the if-modified facet. Slot demons should be made removable.

Thus, the function application-visualization-coupler mentioned above can be defined as follows.

(defmethod application-visualization-coupler ((participant assertion))
  (let ((assertion-gauge (make-two-level-gauge   ; with indirect values
                           (make-indirect-object participant 
                                                 assertion-lower-bound)
                           (make-indirect-object participant 
                                                 assertion-upper-bound))))
    (add-slot-if-modified-demon
      participant  ; object
      'lower-bound ; slot name
      #'(lambda    ; demon function
          (assertion-obj name-of-modified-slot old-value new-value)
          (gauge-update assertion-gauge)))
    (add-slot-if-modified-demon
      participant  ; object
      'upper-bound ; slot name
      #'(lambda    ; demon function
          (assertion-obj name-of-modified-slot old-value new-value)
          (gauge-update assertion-gauge)))
    assertion-gauge))

(defmethod application-visualization-coupler ((participant or-box))
  (make-node "OR"))

Demon functions are closures which provide access to the corresponding visualization object. The gauges for assertion objects (broker, etc.) use indirect values to access assertion objects. The objects representing constraints as nodes are the same as in the class browser example.

Method Demons

Slot demons offer an elegant way of defining slot accesses as interesting events and hence updating corresponding visualization objects. Not only slot accesses are subject to updating a visualization. Every method might define an event of interest. Slot accesses are only special cases. General method demons can be implemented using the meta-object protocol of CLOS. The idea is to wrap a method with a so-called wrapper method which has slots to refer to both the demon functions and the original method (see Figure 10). When all demons are removed the wrapper method itself is removed, too. In this case there is no overhead as with a metaclass which provides own methods for slots accesses (e.g. for indirect values) that overwrite the standard slot accessor methods.

  
Figure 10: Outline of a wrapper method.

A major disadvantage of this wrapping slot accessor is that demons are evaluated for all instances, i.e. they are slot but not instance-specific. Method demons do not solve the problem of compound slot accesses, either. In the following two subsections we propose a solution to these problems.

Instance-Specific Meta-Objects

The CLOS meta-object system assigns to metaclasses the responsibility for both structure (implementation) and behavior of instances. There are other meta-level systems which distinguish between structural and computational meta-objects [26]. In this section we present ideas to provide some kind of dynamic meta-level influence in CLOS [27]. We implement meta-objects as instances of a class standard-meta-object which serves not as a CLOS metaclass. The standard slot access protocol, which uses the method slot-value-using-class, is analogously extended for these ``simple'' meta-objects.

(defclass standard-meta-object ()
  ()
  (:documentation 
   "Class of all meta-objects that provide instance-specific meta behavior."))

(defmethod slot-value-using-meta-object ((mobj standard-meta-object)
                                         object slot-descriptor)
  (call-next-meta-method))

(defmethod (setf slot-value-using-meta-object) ((mobj standard-meta-object)
                                                object slot-descriptor)
  (call-next-meta-method))

Meta-objects can be assigned to instances with metaclass extensible-standard-class. This metaclass describes classes with instances that have one additional or implicit slot called meta-objects (see Figure 11). A set of meta-objects can be assigned to this slot.

  
Figure: Meta-objects for instances with metaclass -standard-class.

An example method handling slot accesses is defined as follows.

(defmethod slot-value-using-class ((class extensible-standard-class)
                                   object slot-descriptor)
  (let ((slot-name (slotd-name slot-descriptor)))
    (if (eq slot-name 'meta-objects) ; prevent recursive slot access
      (if (slot-boundp object 'meta-objects)
        (call-next-method)
        nil)
      (let ((*meta-objects* (slot-value object 'meta-objects))
            (*meta-class-generic-function* #'(lambda () (call-next-method)))
            (*meta-object-generic-function*
              #'(lambda (meta-object)
                  (slot-value-using-meta-object meta-object
                                                object
                                                slot-descriptor))))
       (declare (special *meta-objects*
                         *meta-object-generic-function*
                         *meta-class-generic-function*))
       (if (null *meta-objects*)
         (call-next-method)
         (call-next-meta-method))))))

The function call-next-meta-method is comparable to the function call-next-method. It evaluates slot-value-using-meta-object for the ``next'' meta-object in the list of meta-objects (see Figure 11). The default behavior of slot-value-using-meta-object is to evaluate call-next-meta-method again (s.a.). This default behavior may be augmented or overwritten by subclasses of standard-meta-object (s.b.). When there are no meta-objects (left), call-next-meta-method invokes the ``normal'' slot access functionality of standard-class. There is some additional code needed to enable passing of different parameters to the next metamethod just as with call-next-method.

(defun call-next-meta-method ()
  (declare (special *meta-objects*
                    *meta-object-generic-function*
                    *meta-class-generic-function*))
  (if (endp *meta-objects*)
    (funcall *meta-class-generic-function*)
    (funcall *meta-object-generic-function* (pop *meta-objects*))))

We use these meta-level techniques to extend our constraint example. Using the protocol described above visualizations of particular instances can be provided with little programming effort. For instance, a meta-object could be defined by a class visualizer-meta-object. This class combines a visualization object with a list of interesting slots. Every writing access to these slots is followed by calling the instance-specific visualization object.

(defclass visualizer-meta-object (standard-meta-object)
  ((visualizer :initarg :visualizer
               :accessor visualizer
               :initform #'(lambda (\&rest ignore) nil))
  (interesting-slots :initarg :interesting-slots
                     :reader interesting-slots))
  (:default-initargs :interesting-slots nil))

(defmethod (setf slot-value-using-meta-object) :after (new-value
                                                       (mobj visualizer-meta-object)
                                                       object slot-descriptor)
  (if (member slot-name (interesting-slots mobj))
    (funcall (visualizer mobj) object (slotd-name slot-descriptor))))

Be your-opinion the assertion object of our constraint example (see Figure 9). We add only to this object a corresponding meta-object which prints your-opinion's decision about buying stocks. This behavior can be easily reverted by removing this meta-object from the implicit slot meta-objects.

(add-meta-object your-opinion
                 (make-instance 'visualizer-meta-object
                                :interesting-slots '(lower-bound upper-bound)
                                :visualizer
                                  #'(lambda (assertion slot-name)
                                      (if (> (assertion-lower-bound assertion) 0.75)
                                        (print 'buy) ; or any other visual feedback
                                        (print 'donot-buy)))))

Another behavior might be to temporarily modify a reading access to a slot value. After adding a meta-object of class buying-indicator-meta-object to the object your-opinion, each reading access to the slot lower-bound of your-opinion returns the slot value and a buying indicator.

(defclass buying-indicator-meta-object (standard-meta-object)
  ())

(defmethod slot-value-using-meta-object ((mobj buying-indicator-meta-object)
                                         object slot-name)
  (if (eq slot-name 'lower-bound)
    (let ((slot-value (call-next-meta-method)))
      (if (> slot-value 0.75)
        (values slot-value 'buy)
        (values slot-value 'donot-buy)))
    (call-next-meta-method)))

(add-meta-object your-opinion (make-instance 'buying-indicator-meta-object))

Instance-specific meta-objects have also been proposed by Maes [28,29]. The difference to our approach is that (at least basically) only one meta-object may be assigned to an object at a certain time. One may of course argue that our CLOS implementation is a little impure because of using different mechanisms: metaclasses and meta-objects. Moreover, not all meta-objects may be compatible (see Graube [24] for a discussion). There also remains some overhead even when no meta-objects are attached to an instance.

Compound Events

 

The events of interest mentioned in the previous sections are only defined implicitly, i.e. there are no objects generated to describe events. However, an explicit representation of event conditions is needed to handle more complicated (compound) events. For instance, the situation in our stock example ``all brokers have a lower bound estimation greater than '' might be defined as a compound event. Then, a coordination problem arises since several objects are now concerned. It should even be possible to combine several events (e.g. value changes of different slots in different objects) as a compound event.

We need a declarative specification of such event conditions and a management system responsible for collecting and monitoring announcements of subevents rather than simple demons handling slot accesses directly (see Figure 12).

  
Figure: Instances i-1 to i-4 with slots s-1 to s-3 report new values to an event manager. The agenda is extended at the bottom.

Subevents provide a data structure (in Figure 12 instances of these structures are represented by black dots) for storing event information (e.g. an object, its changed slot, the previous value, and the new value). The management system uses an agenda to access incoming subevents. The supervisor decides when agenda entries can be compiled to a compound event.

The main problem with compound events is the necessity to define the notion of a ``step.'' The definition of a step depends on an ``interpretation'' of the object system, e.g. different step interpretations might exist.

Meta-level programming can be used to define a certain step interpretation for an existing object system. We distinguish two kinds of steps: object steps and system steps. Let's have a look at the constraint example again. We assume that accesses to the slots lower-bound and upper-bound are recorded on the agenda. An entry of the agenda comprises the object together with the corresponding slot values of lower-bound and upper-bound.

An object step is induced when there already exists a slot access entry for a certain slot and the meta-system attempts to add another entry for this slot. If there is an entry for the other slot, the system combines the entries to an object step using the values of the current entry. If there is no such slot entry for the ``other'' slot, the current slot values of the object are used to determine its value. Afterwards, all used agenda entries of the corresponding object are deleted. The new entry is inserted.

System steps are defined analogously, just one level higher. A system steps consists of a list of objects with a sequence of slot values associated to them. As an example we consider two brokers as a (trivial) object ``system.'' A system step is induced when there already exists an object step for one of the brokers and an attempt is made to create another object step just for the same broker object. If no step for the other broker has been recorded, its current slot values are copied and used for the system step. System steps, with the slot values recorded for each object, can be supplied to a visualization component for further processing.

We emphasize that this is only one possible proposal for an event manager, though it is suitable for our stock exchange example.

Other visualization systems (e.g. see in Linden [30]) record the whole agenda (or the stream of events) on a file so that a postmortem visualization can be provided (tractable only for `small' systems).



next up previous
Next: Status of Implementation Up: No Title Previous: The Box Layout




Volker Haarslev
Tue Jun 11 13:34:48 MET DST 1996