Did you know ... | Search Documentation: |
Predicate catch/3 |
The overhead of calling a goal through catch/3
is comparable to
call/1.
Recovery from an exception is much slower, especially if the exception
term is large due to the copying thereof or is decorated with a stack
trace using, e.g., the library library(prolog_stack)
based
on the
prolog_exception_hook/4
hook predicate to rewrite exceptions.
This shows how to convert almost any "string" of 0s and 1s to an integer.
It takes advantage of the built-in term reading, thus avoiding an explicit loop and arithmetic.
binary_string_to_int(Str, Int) :- ( ( string(Str) ; atom(Str) ) -> atomics_to_string(["0b", Str], Literal) ; is_list(Str) -> atomics_to_string(["0b"|Str], Literal) ; type_error(string_type, Str) ), catch(term_string(Int, Literal, []), error(syntax_error(_), _), type_error(string_of_0s_and_1s, Str)).
?- binary_string_to_int('101', X). X = 5. ?- binary_string_to_int([0, 0, 1, 0, 1], X). X = 5. ?- binary_string_to_int(["1", "1", "0"], X). X = 6.
While the Goal argument is correctly named, Catcher is a confusing name for what is the exception term against which a thrown exception shall be against. Recover is a generic goal.
catch(:Goal, +ExceptionTerm, :RecoveryGoal)
This looks much better.
The mode indicator :
indicates that "(The) argument is a meta-argument, for example a term that can be called as goal."
"Recovery from an exception is much slower, especially if the exception term is large due to the copying thereof or is decorated with a stack trace.."
By default, that only happens with catch_with_backtrace/3, but not with catch/3.
Note that the toplevel also prints the backtrace, so it surrounds and query essentially with catch_with_backtrace/3.
Fun with exceptions, ISO-standard and non-ISO-standard (also trying to find a style on how cleanly write code involving exceptions):
See also assertion/1
If you have this program:
main :- catch(domain_error(_,_),Caught,format("Caught: ~q~n",[Caught])), format("Next...~n",[]), catch(assertion(false),Caught,format("Caught: ~q~n",[Caught])), format("There is no next...~n",[]). :- main.
and run it:
$ swipl test.pl Caught: error(domain_error(_41980,_41982),_41976) Next... ERROR: /home/user/test.pl:7: ERROR: Assertion failed: user:false [34] prolog_stack:backtrace(10) at /usr/local/logic/swipl/lib/swipl/library/prolog_stack.pl:487 [33] prolog_debug:assertion_failed(fail,user:false) at /usr/local/logic/swipl/lib/swipl/library/debug.pl:330 ...
Example, catching format/2 complaining about a lack of arguments:
?- catch( % will throw due to "~q" (but still print something) format("Hello, ~w. Goodbye ~q\n",[world]), ExceptionTerm, format("\n\nOh noes! Got an exception: ~q", [ExceptionTerm])).
We get:
Hello, world. Goodbye Oh noes! Got an exception: error(format('not enough arguments'),context(system:format/2,_8118)) ExceptionTerm = error(format('not enough arguments'), context(system:format/2, _8118)).
Perhaps more readably:
p :- WrappedGoal = format("Hello, ~w. Goodbye ~q\n",[world]), RecoveryGoal = format("\n\nOh noes! Got an exception: ~q", [ExceptionTerm]), catch(WrappedGoal,ExceptionTerm,RecoveryGoal).
?- p. Hello, world. Goodbye Oh noes! Got an exception: error(format('not enough arguments'),context(system:format/2,_2352)) true.
Make sure a predicate does not perform any accidental instantiations. For example, this predicate is just supposed to print and do nothing else. At the end, it throws, which rolls back any stray unifications.
print_isolated(X,Throw) :- catch(print_isolated_2(X,Throw),"roll it back",true). print_isolated_2(X,Throw) :- (X=[] -> debug(topic,"X is the empty list",[]) ; true), % ERROR: "=" instead of "==" (Throw == true -> throw("roll it back") ; true). test_print_isolated(X,Throw) :- debug(topic), % switch on debug printing for topic "topic" debug(topic,"X before call: ~q",[X]), print_isolated(X,Throw), debug(topic,"X after call: ~q",[X]).
And so:
?- test_print_isolated(X,true). % X before call: _16 % X is the empty list % X after call: _16
?- test_print_isolated(X,false). % X before call: _3822 % X is the empty list % X after call: [] X = [].
As an alternative to throw/catch to roll back all bindings:
\+ \+
Example for when you want to call main
of your source file immediately after the source file has been loaded (from swipl-devel/man/select.pl
):
:- initialization ( catch(main, E, ( print_message(error, E), fail )) -> halt ; halt(1) ).