The built-in predicate catch/3
enables you to handle
exceptions to a specific goal:
catch(
:ProtectedGoal,
?ExceptionTerm,
:Handler)
ISOProtectedGoal is executed. If all goes well, it will behave just
as if you had written call(
ProtectedGoal)
instead. If an
exception is thrown while ProtectedGoal is running, Prolog will
abandon ProtectedGoal entirely. Any bindings made by
ProtectedGoal will be undone, just as if it had failed. If the
exception occurred in the scope of a
call_cleanup(
Goal,
Cleanup)
, Cleanup will be
called.
Side-effects, such as asserts and retracts,
are not undone, just as they are not undone when a goal fails. After
undoing the bindings, Prolog tries to unify the exception term thrown
with the ExceptionTerm argument. If this unification succeeds,
Handler will be executed as if you had written
ExceptionTerm=<the actual exception term>, Handler
If this unification fails, Prolog will keep searching up the ancestor
list looking for another exception handler. If during this search it
reaches a recursive call to Prolog from C, the recursive calls returns
with an uncaught exception. If it reaches the top-level (or a break
level), an appropriate error message is printed (using
print_message/2
).
ProtectedGoal need not be determinate. That is, backtracking into
ProtectedGoal is possible, and the exception handler becomes reactivated
in this case. However, if ProtectedGoal is determinate, the call
to catch/3
is also determinate.
The ProtectedGoal is logically inside the catch/3
goal, but the Handler is not. If an exception is thrown inside
the Handler, this catch/3
goal will not be
reactivated. If you want an exception handler that protects itself,
you have to program it, perhaps like this:
recursive_catch_handler(Err, Goal, Handler) :- catch(Goal, Err, recursive_catch_handler(Err, Handler, Handler)).
See mpg-ref-catch.
Certain built-in and library predicates rely on the exception mechanism, so it is usually a bad idea to let Pattern be a variable, matching any exception. If it must be a variable, the Handler should examine the exception and pass it on if it is not relevant to the current invocation.