Suppose you want a routine that is given a filename and a prompt string. This routine is to open the file if it can; otherwise it is to prompt the user for a replacement name. If the user enters an empty name, it is to fail. Otherwise, it is to keep asking the user for a name until something works, and then it is to return the stream that was opened. (There is no need to return the file name that was finally used. We can get it from the stream.)
:- use_module(library(prompt), [ prompted_line/2 ]). open_output(FileName, Prompt, Stream) :- on_exception(Error, open(FileName, write, Stream), ( file_error(Error) -> print_message(warning, Error), retry_open_output(Prompt, Stream) ; raise_exception(Error) )). file_error(domain_error(open(_,_,_), 1, _, _, _)). file_error(existence_error(open(_,_,_), 1, _, _, _)). file_error(permission_error(open(_,_,_), _, _, _, _)). retry_open_output(Prompt, Stream) :- prompted_line(Prompt, Chars), atom_chars(FileName, Chars), FileName \== '', open_output(FileName, Prompt, Stream).
What this example does not catch is as interesting as what it does. All instantiation errors, type errors, context errors, and range errors are re-raised, as they represent errors in the program.
As the previous example shows, you generally do not want to catch all exceptions that a particular goal might raise.