Does Prolog have a restart state and system, such as Common Lisp?

General Lisp allows exception handling through conditions and restarts . In crude terms, when a function throws an exception, a “catcher” can decide how and whether a “thrower” should be executed. Does Prolog offer a similar system? If not, is it possible to build on top of existing predicates for walking and exploring the call stack?

+6
source share
4 answers

The ISO / IEC Prolog standard provides only a very rudimentary mechanism for handling exceptions and errors, which is more or less comparable to what Java offers, and far from it. General Lisp is a rich mechanism, but there are still some points worth noting. In particular, in addition to the real signaling and processing mechanism, many systems provide a mechanism similar to unwind-protect . That is, a way to ensure that the goal will be fulfilled even if there are unprocessed signals.

ISO throw / 1, catch / 3

An exception is thrown / thrown using throw(Term) . First, a copy of Term is created using copy_term/2 , which calls its Termcopy , and then this new copy is used to find the corresponding catch(Goal, Pattern, Handler) , the second argument of which is combined with Termcopy . When the Handler is executed, all unifications caused by Goal are canceled. Thus, for Handler there is no access to the lookups that are present when throw/1 executed. And there is no way to continue working in the place where throw/1 was executed.

Errors of built-in predicates are signaled by running throw(error(Error_term, Imp_def)) , where Error_term corresponds to one of the ISO error classes and Imp_def can provide the implementation of certain additional information (for example, the source file, line number, etc.).

There are many cases where error handling locally would be very useful, but many developers find it too complicated to implement.

The extra effort that the Prolog processor handles every local error is pretty significant and much more so than in Common Lisp or other programming languages. This is due to the very nature of the association in Prolog. Local error handling would require the cancellation of unifications that are performed during the execution of the built-in: the implementation has two possibilities for implementing this:

  • create a “selection point” at the time of invoking the built-in predicate, this entails a lot of additional overhead, both for creating this selection point and the “back” subsequent bindings
  • execute each built-in predicate manually and decide in each case to handle errors - while this is the most efficient in terms of execution time overhead, it is also the most expensive and error-prone approach

Similar difficulties are caused by the use of WAM registers inside the built-in modules. Again, there is a choice between a slow system or one with significant overhead.

exception_handler / 3

Many systems, however, provide internal mechanisms, but only a few offer them sequentially to the programmer. IF / Prolog provides exception_handler/3 , which has the same arguments as catch/3 , but handles the error or exception locally:

  [user]? - catch ((arg (a, f (1), _); Z = ok), error (type_error (_, _), _), fail).

 no

 [user]? - exception_handler ((arg (a, f (1), _); Z = ok), error (type_error (_, _), _), fail).

 Z = ok

 yes

setup_call_cleanup / 3

This built-in module offers many systems. It is very similar to unwind-protect , but requires some additional complexity due to the Prolog backtracking mechanism. See His current definition .


All these mechanisms must be provided by the Implementor system; they cannot be built on top of the ISO Prolog.

+5
source

The ISO prolog defines these predicates:

  • throw/1 , which throws an exception. An argument is an exception that must be thrown (any term)
  • catch/3 , which executes the target and catches some exceptions, in which case it executes an exception handler. The first argument is the target to be called, the second argument is the exception pattern (if the exception thrown by throw/1 is combined with this pattern, the handler target is executed), and the third argument is the target of the handler.

Usage example:

 test:- catch(my_goal, my_exception(Args), (write(exception(Args)), nl)). my_goal:- throw(my_exception(test)). 

Regarding your note, “If not, is it possible to build on top of existing predicates to walk and examine the call stack?” I do not think there is a general way to do this. Perhaps look at the prolog system documentation that you use to find out if there is a way to go through the stack.

+1
source

As stated in his answer, ISO Prolog does not allow this. However, some experiments show that SWI-Prolog provided a mechanism on which conditions and reloads can be built. The following is a very crude proof of concept.

the "catcher" calls restart/2 to call the target and provides a predicate for choosing among the available reboots if the condition is to be raised. The thrower calls signal_condition/2 . The first argument is a condition to increase. The second argument will be bound to the selected restart. If no restart is selected, the condition becomes an exception.

 restart(Goal, _) :- % signal condition finds this predicate in the call stack call(Goal). signal_condition(Condition, Restart) :- prolog_current_frame(Frame), prolog_frame_attribute(Frame, parent, Parent), signal_handler(Parent, Condition, Restart). signal_handler(Frame, Condition, Restart) :- ( prolog_frame_attribute(Frame, goal, restart(_, Handler)), call(Handler, Condition, Restart) -> true ; prolog_frame_attribute(Frame, parent, Parent) -> signal_handler(Parent, Condition, Restart) ; throw(Condition) % reached top of call stack ). 
+1
source

You can use hypothetical reasoning to realize what you want. Let's say the Prolog system, which allows hypothetical reasoning, supports the following inference rule:

 G, A |- B ----------- (Right ->) G |- A -> B 

There are some Prolog systems that support this, such as lambda Prolog . Now you can use hypothetical arguments to implement, for example, restart / 2 and signal_condition / 3. Suppose hypothetical reasoning (-:) / 2, then we could:

 restart(Goal,Handler) :- (handler(Handler) -: Goal). signal_condition(Condition, Restart) :- handler(Handler), call(Handler,Condition,Restart), !. signal_condition(Condition, _) :- throw(Condition). 

The solution will not in vain trace the entire stack trace, but directly the request for the handler. But he asks whether I need a special Prolog or whether I can do hypothetical reasoning on my own. In a first approximation (-:) / 2 can be implemented as follows:

 (Clause -: Goal) :- assume(Clause), Goal, retire(Clause). assume(Clause) :- asserta(Clause). assume(Clause) :- once(retact(Clause)). retire(Clause) :- once(retract(Clause)). retire(Clause) :- asserta(Clause). 

But the above will not work correctly if Goal throws out or throws an exception. Thus, the best solution is available, for example, in Jekejeke Minlog 0.6:

 (Clause -: Goal) :- compile(Clause, Ref), assume_ref(Ref), Goal, retire_ref(Ref). assume_ref(Ref) :- sys_atomic((recorda(Ref), sys_unbind(erase(Ref)))). retire_ref(Ref) :- sys_atomic((erase(Ref), sys_unbind(recorda(Ref)))). 

The sys_unbind / 1 predicate sends the undo target in the binding list. This corresponds to cancellation / 1 from SICStus. The binding list is resistant to cuts. Sys_atomic / 1 ensures that the cancellation target is always a schedule, even if an external signal occurs during execution, for example, for example, the subscriber issues an interrupt. This corresponds to, for example, the first argument from setup_call_cleanup / 3.

The advantage of using links to sentences here is that the proposal is only compiled once, even if there is a rollback between the target and the continuation after (-:) / 2. But otherwise, the solution is likely to be slower than the target in the stack trace, calling it. But you can imagine further refinements of the Prolog system, for example (-:) / 2 as a primitive and appropriate compiler methods.

Bye

+1
source

Source: https://habr.com/ru/post/926445/


All Articles