Prolog And Tcl Interact through Prolog Event Queue

In the previous two methods, one of the language systems was the master and the other slave, the master called the slave to perform some action or calculation, the slave sits waiting until the master calls it. We have seen that this has disadvantages when Prolog is the slave in that the state of the Prolog call is lost. Each Prolog call starts from the beginning unless we save the state using message database manipulation through calls to assert and retract.

Using the Prolog event queue, however, it is possible to get a more balanced model where the two language systems cooperate without either really being the master or the slave.

One way to do this is the following:

What can processing a Prolog event mean? Well, for example, a button press from Tk could tell the Prolog program to finish or to start processing something else. The Tcl program is not making an explicit call to the Prolog system but sending a message to Prolog. The Prolog system can pick up the message and process it when it chooses, in the meantime keeping its run state and variables intact.

To illustrate this, we return to the 8-queens example. If Tcl/Tk is the master and Prolog the slave, then we have shown that using a callback to Prolog, we can imagine that we hit a button, call Prolog to get a solution and then display it. But how do we get the next solution? We could get all the solutions, and then use Tcl/Tk code to step through them, but that doesn't seem satisfactory. If we use the Prolog is the master and Tcl/Tk is the slave model then we have shown how we can use Tcl/Tk to display the solutions generate from the Prolog side: Prolog just make a call to the Tcl side when it has a solution. But in this model Tcl/Tk widgets do not interact with the Prolog side; Tcl/Tk is mearly an add-on display to Prolog.

But using the Prolog event queue we can get the best of both worlds: Prolog can generate each solution in turn as Tcl/Tk asks for it.

Here is the code on the Prolog side that does this. (We have left out parts of the code that haven't changed from our previous example, see Queens Display).

     :- use_module(library(tcltk)).
     :- use_module(library(lists)).
     
     setup :-
         tk_new([name('SICStus+Tcl/Tk - Queens')], Tcl),
         tcl_eval(Tcl, 'source queens2.tcl', _),
         tk_next_event(Tcl, Event),
         (   Event = next -> go(Tcl)
         ;   closedown(Tcl)
         ).
     
     closedown(Tcl) :-
         tcl_delete(Tcl).
     
     go(Tcl) :-
         tcl_eval(Tcl, 'clear_board', _),
         queens(8, Qs),
         show_solution(Qs),
         tk_next_event(Tcl, Event),
         (   Event = next -> fail
         ;   closedown(Tcl)
         ).
     go(Tcl) :-
         tcl_eval(Tcl, 'disable_next', _),
         tcl_eval(Tcl, 'clear_board', _),
         tk_next_event(Tcl, _Event),
         closedown(Tcl).
     
     show_solution(Tcl, L) :-
         tcl(Tcl),
         reverse(L, LR),
         tcl_eval(Tcl, [show_solution, br(LR)], _),
         tk_do_all_events.
     

Notice here that we have used tk_next_event/2 in several places. The code is executed by calling setup/0. As usual, this loads in the Tcl part of the program, but then Prolog waits for a message from the Tcl side. This message can either be next, indicating that we want to show the next solution, or stop, indicating that we want to stop the program.

If next is received, then the program goes on to execute go/1. What this does it to first calculate a solution to the 8-queens problem, displays the solution through show_solution/2, and then waits for another message from Tcl/Tk. Again this can be either next or stop. If next, the the program goes into the failure part of a failure driven loop and generates and displays the next solution.

If at any time stop is received, the program terminates gracefully, cleaning up the Tcl interpreter.

On the Tcl/Tk side all we need are a couple of buttons: one for sending the next message, and the other for sending the stop message.

     button .next -text next -command {prolog_event next}
     pack .next
     
     button .stop -text stop -command {prolog_event stop}
     pack .stop
     

(We could get more sophisticated. We might want it so that when the button it is depressed until Prolog has finished processing the last message, when the button is allowed to pop back up. This would avoid the problem of the user pressing the button many times while the program is still processing the last request. We leave this as an exercise for the reader.)