|
Predicate ValuesVisual Prolog supports a notion of predicate values. Predicate values are predicates that can be treated as values in the sense that:
Of course, like "plain" predicates the predicate values can be called with appropriate arguments. Note that predicate values are declared as instances of predicate domains (please, refer to the online Help topic "Predicate Domain Declarations" for details).
Predicate values have many usages. One of the most important is for callbacks. A callback is a predicate that is used to call back from some used entity to the user of this entity. For example:
Callbacks are normally used for one or both of the following purposes:
When dealing with asynchronous events a program registers a callback with some event source. Then this event source invokes the callback whenever an event occurs. "Data ready" in asynchronous communication is a typical example of such an asynchronous event. Another very typical example is a Windows event handler. As an example of advanced/dynamic parameterization assume a tool that can create a certain kind of window. This window has the ability to change the shape of the cursor (mouse pointer) when it enters certain parts of the window. The window is however intended to work in many different situations, and therefore it cannot know which cursor to use in which parts of the window. In fact, the choice of cursor might depend on numerous things of which the window has no knowledge at all. Subsequently the window simply leaves the choice of cursor to the program that uses the window. And the way the window does this is by invoking a callback predicate. Via this callback predicate the window asks the surrounding program what cursor to use, when the mouse enters a certain part of the window. Since the window makes such a callback each time the mouse enters a certain part it need not receive the same cursor each time, the choice of cursor can dynamically depend on things external to the window. Object Predicate ValuesVisual Prolog supports a notion of object predicate values. Object predicate value is a generalization of predicate values (and as such the reader is encouraged to understand that notion first). An object predicate value is a non-static predicate of a specific object. This is opposed to "ordinary" predicate values, which are either global, local or static predicates (predicate values). Notice that object predicate values are declared as instances of object predicate domains (please refer to the online Help topic "Object Predicate Domain Declarations" for details). Seen from the outside, an object predicate value looks like an "ordinary" predicate value, because the object to which it belongs is subsumed by the predicate value itself. I.e. an object predicate value consists of both the code and the object to which the code belongs. Therefore, the call of an object predicate value looks exactly the same as the call of an "ordinary" predicate value: It is simply applied to their arguments. But execution will (nevertheless) take place in the context of the specific object to which the object predicate value relates. Therefore, the main reason for using object predicate values over "ordinary" predicate values is that execution will occur in a specific context of an object. To illustrate this semantics let us consider an example: First, we declare an object predicate domain (please refer to the online Help topic "Object Predicate Domain Declarations" for details): global domains objectIntInt = object procedure integer (integer) The domain objectIntInt declares object (non-static) predicates from integer to integer with procedure mode. Now let us declare a predicate of this domain. Because a predicate value of such domain must be a non-static member predicate, therefore, it must be declared in a class: class cLast predicates last : objectIntInt endclass cLast To really illustrate that last is indeed a non-static member predicate, we let it returns a value dependent on the state of the object. In fact we will let it returns the parameter it was called with at the previous invocation. So, we store the parameter from one invocation to the next in a fact: implement cLast facts % invariant: "lastParamater" holds % the parameter value from last invocation of "last" % initially assume 0 as value from "last" invocation single lastParameter(integer Last) clauses lastParameter(0). last(ThisParameter, LastParameter) :- lastParameter(LastParameter), assert(lastParameter(ThisParameter)). endclass cLast So far, the only thing special about this example is the way the predicate last is declared. And before we will really use last as a an object predicate value, let us use it like a normal non-static member predicate: predicates test1() - procedure () clauses test1() :- O1 = cLast::new(), O2 = cLast::new(), _1 = O1:last(1), _2 = O2:last(2), V1 = O1:last(3), V2 = O2:last(4), writef("V1 = %, V2 = %", V1, V2), nl, O1:delete(), O2:delete(). If we call test1 then it will first create two objects O1 and O2. Then it calls last on O1 with parameter 1 and on O2 with parameter 2. Both O1 and O2 will store their parameter in respective instances of lastParameter fact. So, when last is called again we will retrieve 1 and 2, respectively. Subsequently, the call of test1 will produce the output: V1 = 1, V2 = 2 Now let us do the same again, only this time we will use the predicates as values held in variables: predicates test2() - procedure () clauses test2() :- O1 = cLast::new(), O2 = cLast::new(), P1 = O1:last, % P1 is bound to an instance of % the object predicate domain objectIntInt P2 = O2:last, _1 = P1(1), _2 = P2(2), V1 = P1(3), V2 = P2 (4), writef("V1 = %, V2 = %", V1, V2), nl, O1:delete(), O2:delete(). The first thing to notice is that object predicate values consist of both an object (O1 or O2) and a predicate (last). The call of an object predicate value is, however, completely identical to calls of "ordinary" predicate values, i.e. you do not apply an object predicate value to an object, you simply call it with appropriate arguments. The effect of running test2 is exactly the same as when running test1: Executing P1(1) will store 1 in the lastParameter fact of O1. Likewise, the next call of P1 will retrieve the value stored in the lastParameter fact of O1. And completely similarly P2 will refer to O2. Object predicate values are at least as useful for callbacks as "ordinary" predicate values (please refer to the description of predicate values for a discussion of callbacks). The benefit from using object predicate values (over "ordinary" predicate values) is that the callback comes back to a specific context, namely to the object to which the callback belongs. This makes it possible to deal with several different callbacks of the same kind because each callback will end up in its own context. Let us walk through an example. Assume that we have a number of "things" that are interested in knowing when a certain value changes. (For the sake of the example, this value is simply an integer.) These things want to be notified asynchronously about changes in the value. Therefore, they register a "dataReady" listener at a data ready source. In this example, we choose to transfer the new value together with the data ready notification, but with more complex data we might let the listener pick up the data itself. We represent the data ready source by an object of class cDataReadySource. If we have several pieces of data that can "become ready", then we can use one instance of cDataReadySource per data piece, making it possible to listen to notifications for exactly those of interest. cDataReadySource supports registering and unregistering of listeners. It also has a predicate for setting the value in question. Listener are represented by object procedure values (i.e. object callbacks). class cDataReadySource domains dataReadyListener = object procedure (cDataReadySource EventSource, integer NewValue) predicates addDataReadyListener(dataReadyListener Listener) - procedure (i) removeDataReadyListener(dataReadyListener Listener) - procedure (i) predicates setValue(integer NewValue) - procedure (i) endclass cDataReadySource The implementation is quite straightforward. We store the currently registered listeners in a fact, and, when the data is changed, we notify all registered listeners of this. implement cDataReadySource facts % Invariant: listener_db contains the currently % registered listeners (multiple registrations are ignored) listener_db(dataReadyListener Listener) clauses addDataReadyListener(Listener) :- listener_db(Listener), % already registered !. addDataReadyListener(Listener) :- assert(listener_db(Listener)). removeDataReadyListener(Listener) :- retractAll(listener_db(Listener)). predicates dataIsReady(integer NewValue) - procedure (i) clauses dataIsReady(NewValue) :- this(This), listener_db(Listener), Listener(This, NewValue), fail. dataIsReady(_). clauses setValue(NewValue) :- dataIsReady(NewValue). endclass cDataReadySource Let us also try to use the class above in a context. Assume that we have a system, which counts how many users are active, this count is used in a number of places. One of these places is a status window, which displays the count. For the sake of the example, we imagine that there is a global predicate getUserCountSource, which will return a cDataReadySource object corresponding to the count. global predicates cDataReadySource getUserCountSource() - procedure () We implement our status window as a class cStatusWindow. The declaration of cStatusWindow is not very interesting in this context; all we are concerned with is the implementation. In the implementation we put our dataReadyListener, and in the constructor of the class we register this listener with the user count data source. We, of course, also unregister the listener in the destructor. implement cStatusWindow predicates updateWindow(integer NewValue) - procedure (i) clauses updateWindow(NewValue) :- … predicates onUserCountChanged : cDataReadySource::dataReadyListener clauses onUserCountChanged(_Source, NewValue) :- updateWindow(NewValue). clauses new() :- UserCountSource = getUserCountSource(), UserCountSource:addDataReadyListener(onUserCountChanged). % THIS is subsumed delete() :- UserCountSource = getUserCountSource(), UserCountSource:removeDataReadyListener(onUserCountChanged). endclass cStatusWindow No matter how many status windows we create they all will be updated when the user count changes. |