PrologEZ
Control & the database ยท Lesson 32 of 43

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:

Formalmeaning
instantiation_errora 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.