Interpreter hook for accessing failed goals?

I’m using: SWI-Prolog version 9.2.4

I want the code to: process top-level queries and answers (like logging them). expand_query/4 and expand_answer/3 are helpful in this respect, as they give access to the original query and to the answer. Is there a similar hook that would give me, on a failing query, the original goal? Another solution would be a hook that would give me the query and the bindings, but instead of expecting to get the control back would hand over control to me. I’d execute the goal and then inform the interpreter about the result, such that it can display the answer in the usual form (and allow the user to reference values with the $var syntax etc). (If there is already such an interface to the query parser and answer generator, I missed it).

In the code below, how could I invoke logFailure when Goal fails? Are there any alternate approaches? Last but not least: Is there an approach more portable than the one below, given that it doesn’t even work for SWI prolog below 9.x, as expand_query/4 has been added only recently?

start_logging :-
    assert(expand_query(Query,Query,Bindings,Bindings) :- logQuery(Query,Bindings)),
    assert(prolog:expand_answer(Goal,Bindings,Bindings) :- user:logAnswer(Goal,Bindings)).

stop_logging :-
	retract(expand_query(Query,Query,Bindings,Bindings):-logQuery(Query,Bindings)),
	retract(prolog:expand_answer(Goal,Bindings,Bindings):-user:logAnswer(Goal,Bindings)).

logQuery(Query,Bindings). % process query

logAnswer(Goal,Bindings). % process answer on success

logFailure(Goal). % process the fact that Goal failed.

You can intercept the message system using message_hook/3. The relevant message is query(Term). See boot/messages.pl, query_result/1 for the default implementation. You’ll get something like this:

:- multifile user:message_hook/3.

user:message_hook(query(no), _Kind, _Lines) :-
    <query failed>,
    fail.      % Make hook fail, so default is executed.

Note that if you want to save the query, you can use b_setval/2 to store it in a global variable. That is better than e.g., assertz/1 as it does not copy the term and thus preserves variables.

Note that the message interception is not very robust in two ways. If there is a hook for the same thing that succeeds before your hook, your hook is not called. While the hook interception is stable, the exact message terms are not documented and their stability is not guaranteed.

1 Like

Just a clarification question, so is there is an “accidental” but stable order in which multiple hooks will be evaluated? And unless they all “fail” as in your example, later hooks will just be skipped?

The hook predicate is a multifile predicate. Clauses are tried in the normal Prolog order. If one succeeds, the hook is considered completed. If all fail, some default action is performed. That is how most hooks work. Roughly, the implementation typically follows this skeleton:

:- multifile p_hook/1.

p(X) :- p_hook(X), !.
p(X) :- <default behaviour>

The problem with multifile predicates is that you typically do not know what has already be loaded nor what will be loaded. In addition, if you reload a file holding clauses for a multifile predicate, the new clauses typically end up as last. In fact, I’m not entirely sure about this. In (very) old versions, reloading a file would wipe all clauses loaded from the file first. Currently, the system tries to preserve unmodified clauses and performs an atomic update from the old clause set to the new after the reload completes. So, a new or modified clause surely ends up at the end. Unmodified clauses may stay where they are, but be aware that the “no change” logic is rather simple minded. For non-multifile predicates all remains predictable and a missed opportunity to reuse a clause merely temporarily costs some memory. For multifile predicates the ordering of clauses gets yet more unpredictable if reloading is involved.

1 Like

Thanks a lot!