Programming in XPCE/Prolog: Section 10.2

## 10.2 Executable objects

PCE defines executable objects. Executable objects (called code objects) can be compared to lambda functions in Lisp. They are used in the contexts given below.

- Definition of `call-back' actions
  The most common usage of executable objects is to specify the action undertaken by interactive UI components when they are activated. We have seen examples of this in section 2.8.
- As parameter to a method
  Various methods take an executable object as a parameter to specify the action undertaken. Suppose we want to remove all graphicals in the selection of a graphical device. The method `device`<-`selection' returns a chain object containing all graphicals currently selected on the device. The implementation is:

      ...,
      send(Dev?graphicals, for_all, message(@arg1, free)).
      ...

  The method `chain`->`for_all' will execute the argument message object in a loop for all elements in the chain. It will bind @arg1 to the currently processed element of the chain.

Two major groups of code objects are distinguished: procedures and functions. The first only returns status information similar to the send-operation. The latter returns a value or failure status information similar to the get-operation.

### 10.2.1 Procedures

Procedures perform an action when executed. Similar to Prolog, the execution of a procedure either returns successful or failed completion. Procedures are normally executed by the object or method they are handed to. For example, the procedure associated to a button object is executed when the button is pressed. The procedure handed to the `chain`->`for_all' method we have seen above is executed by this method for each element of the chain.

Procedures can also be executed explicitly using the method `code`->`forward'. This binds the argument-forwarding objects @arg1, ..., and then executes the procedure and restores the old bindings of argument-forwarding objects. For example:

    1 ?- new(@m, message(@prolog, format, 'Hello ~w~n', @arg1)).
    2 ?- send(@m, forward, world).
    Hello world

- message(Receiver, Selector, Argument ...)
  A message object starts a send-operation when executed. The arguments of a message are either objects or functions. In the latter case these are evaluated before the message itself is evaluated. If the evaluation of one of the argument functions fails, the message is not executed. The Receiver of a the message can be @prolog to invoke a predicate in the Prolog environment.
- and(Statement, ...)
  An and is a sequence of code objects. It fails if one of the members fails and succeeds otherwise. If a statement is a function, it will be evaluated. Functions fail only if they return the fail control-value. Notably, a function that returned the boolean @off (false) is considered to have succeeded.
- if(Condition, [Then], [Else])
  An if implements a branch. It first evaluates Condition and then either of Then or Else. The success of the entire if is determined by the success of the executed Then or Else. Either or both of these statements may be omitted. The construct `if(Statement)` may be used to force success of a code object.
- Conditions: (A `==` B, A `\==` B, A `<` B, ...)
  These executable objects are commonly used as conditions for the if object.

The online manual may be used to get an overview of the other available code objects. See section 3.3.4.

### 10.2.2 Functions

Functions are code objects which ---when executed--- evaluate to a value. They are commonly used as arguments to other code objects or as arguments to any method. A function is automatically evaluated iff:

- It appears as part of a code object that is executed
- Type checking demands execution

The most important function objects are:

- ?(Receiver, Selector, Argument, ...)
  Class ? starts a get-operation when executed (= evaluated). The classname is pronounced as obtainer. All the arguments may be functions. These will be evaluated before the get-operation is performed. As with message, a failing evaluation of one of the arguments forces failure of the whole.
- var
  Class var describes a variable. The objects @arg1, @arg2, ..., @arg10, @receiver and @event are the most commonly used var objects.

  The objects @arg1, ... are the message forwarding arguments. Assume the following message.

      ?- new(@m, message(@pce, format,
                         'Hello %s\n', @arg1)).

  When executed, @arg1 (a function) is first evaluated. The method `code`->`forward: argument, ...' binds these var objects locally:

      ?- send(@m, execute),
         send(@m, forward, world),
         send(@m, execute).
      @default
      world
      @default

  The objects @receiver and @event are functions referring to the receiver of the currently executing method and the current user-event. Var objects are rarely created explicitly by application programmers.

- arithmetic functions (+, -, *, /)
  These functions define integer arithmetic and are commonly used to deal with graphical computations in code objects.
- create(Class, InitArg, ...)
  This function evaluates to a new instance of Class from the given InitArg initialisation arguments. Its usage is best explained using an example. Consider we want to iterate over all boxes in a graphical device, writing a file with lines of the form

      BOX <name> at X, Y

  A naive approach and common beginners mistake is to write:

              ...,
              new(F, file(Output)),
              send(F, open, write),
              send(Dev?graphicals, for_all,
                   if(message(@arg1, instance_of, box),
                      message(F, append,
                              string('BOX %s at %d,%d\n',
                                     @arg1?name,
                                     @arg1?x,
                                     @arg1?y)))),
              send(F, close),
              ...

  This example will yield a warning:

      [PCE error: get: No implementation for:
                              @default/constant <-x
              in: get(@default/constant, x)]
      PCE:  4 fail: get(@default/constant, x) ?

  The reason for this is that the interface will translate string(...) into an instance of class string, instead of the execution of the `->`for_all method's body creating different strings for each box displayed on the device. The correct solution is to use:

              ...,
              new(F, file(Output)),
              send(F, open, write),
              send(Dev?graphicals, for_all,
                   if(message(@arg1, instance_of, box),
                      message(F, append,
                              create(string,
                                     'BOX %s at %d,%d\n',
                                     @arg1?name,
                                     @arg1?x,
                                     @arg1?y)))),
              send(F, close),
              ...

  The construct `create(...)' will create an instance of class create. `message`->`execute' will first evaluate all functions in the message and thus create an instance of class string.

### 10.2.3 Example 1: Finding objects

A common problem is to find objects, notably some specific graphical object on a window. If we want to find all instances of class box within a graphical device, we can use the call below, collecting all boxes on the device in a new chain.

            ...
            get(Dev?graphicals, find_all.
                message(@arg1, instance_of, box),
                Boxes),
            ...

### 10.2.4 Example 2: Internal behaviour of dialog window

Code are most commonly used to specify the internal behaviour of dialog windows, such that the call-back predicate can concentrate on the real function. We have seen an example of this in section 2.8.

Below there is another example. Data is assumed to be an XPCE string object containing a PostScript® description of a graphical object as can be obtained using

            ...,
            get(Graphical, postscript, PostScriptString),
            ...,

In this example both the internal dynamics of the dialog window (the label of the text-entry fields changes if the user switches from file to printer) and grabbing the arguments from the various dialog items is written using XPCE executable objects. Prolog will only be called to do the real work: printing the data to the requested destination.

Note that XPCE/Prolog does not require you to use XPCE executable objects this way. It is also possible to call Prolog from both the menu and the buttons, passing the dialog window as argument and write all behaviour in Prolog. We leave this as an exercise to the user.

    postscript(Data) :-
            new(D, dialog('Print destination')),
            send(D, append, new(T, menu(destination, marked))),
            send_list(T, append, [printer, file]),
            send(T, layout, horizontal),
            send(D, append,
                 new(A, text_item(printer_name, 'PostScript'))),
            send(T, message,
                 if(T?selection == printer,
                    message(A, label,
                            ?(A, label_name, printer_name)),
                    message(A, label,
                            ?(A, label_name, file_name)))),
            send(D, append,
                 button(ok, and(message(@prolog,
                                        print_postscript,
                                        T?selection,
                                        A?selection,
                                        Data),
                                message(D, destroy)))),
            send(D, append,
                 button(cancel, message(D, destroy))),
            send(D, default_button, ok),
            send(D, open).

    print_postscript(printer, Address, Data) :- !,
            new(F, file),
            send(F, open, write),
            send(F, append, Data),
            send(F, close),
            get(F, name, TmpFile),
            get(string('lpr -P%s %s', Address, TmpFile),
                value, Command),
            unix(shell(Command)),
            send(F, remove).
    print_postscript(file, Address, Data) :-
            new(F, file(Address)),
            send(F, open, write),
            send(F, append, Data),
            send(F, close).

Figure 15 : Print destination dialog using code objects for internal behaviour