Errors & exceptions
catch/3, throw/1, and writing predicates that fail gracefully.
Failure ("no") is a normal outcome in Prolog. An exception is different: something is wrong, and execution should abandon the current computation until a handler is found.
throw(Ball) raises an exception; catch(Goal, Pattern, Recovery) runs Goal and, if it throws a ball that unifies with Pattern, runs Recovery instead.
?- catch(throw(oops), Ball, true)?- catch(X is 1 / 0, error(E, _), true)?- catch(atom_length(X, _), error(Err, _), true)?- catch(atom_length(123456, 3.5), error(Err, _), true)Built-in predicates throw terms of the shape error(Formal, Context), where Formal is one of the standard descriptions:
Formal | meaning |
|---|---|
instantiation_error | a variable was not bound enough |
type_error(Type, Value) | wrong kind of value |
domain_error(Domain, Value) | right type, unacceptable value |
existence_error(Kind, Thing) | e.g. calling an unknown predicate |
evaluation_error(What) | arithmetic trouble |
Uncaught exceptions are reported in red, as you have seen:
?- atom_length(X, 3)?- foo(1, 2)Throwing your own
Throw anything, but conventional error(...) terms integrate with Prolog's message system:
withdraw(Balance, Amount, New) :-
( \+ number(Amount)
-> throw(error(type_error(number, Amount), withdraw/3))
; Amount > Balance
-> throw(insufficient_funds(Balance, Amount))
; New is Balance - Amount
).
try(Goal) :-
catch(Goal, Problem, (format("caught: ~q~n", [Problem]), fail)).?- withdraw(100, 30, New)?- try(withdraw(100, 500, _))?- try(withdraw(100, ten, _))Check your inputs with must_be/2
must_be(Type, Value) throws the right standard error for you:
?- catch(must_be(positive_integer, -3), error(E, _), true)?- catch(must_be(integer, _), error(E, _), true)Cleaning up
setup_call_cleanup(Setup, Goal, Cleanup) guarantees Cleanup runs, whether Goal succeeds, fails or throws. It's how you'd close a file you opened.
?- catch(setup_call_cleanup(write(open), throw(boom), (write(" closed"), nl)), boom, write(recovered))Exercise: Safe division
Write safe_div(X, Y, Result) using integer division //. If the division throws an exception (e.g. division by zero), Result should be the atom undefined instead.