Quintus
Prolog Manual
Whenever the Prolog system encounters a situation where it cannot continue execution, it raises an exception. For example, if a built-in predicate detects an argument of the wrong type, it raises a type_error exception. The reference page description of each built-in predicate lists the kinds of exceptions that can be raised by that built-in predicate. The default effect of raising an exception is to terminate the current computation and then print an error message. After the error message, you are back at Prolog's top level. For example, if the goal
X is a/2
is executed somewhere in a program you get
! Type Error in argument 2 of is/2 ! number expected, but a found ! goal: A is a/2 | ?-
Particular things to notice in this message are:
You can raise exceptions from your own code using the built-in predicate
raise_exception(+ExceptionCode)
The argument to this predicate is the exception code; it is an arbitrary non-variable term of which the principal functor indicates the exception class. You can use the same exception classes as the system (see {manual(g-19-4)}), or you can use your own exception classes. Error messages like the one shown above are printed using the built-in predicate print_message/2. One of the arguments to print_message is the exception code. print_message can be extended, as described in {manual(g-20)}, so that you can have appropriate error messages printed corresponding to your own exception classes.
It is possible to protect a part of a program against abrupt termination in the event of an exception. There are two ways to do this:
The built-in predicate on_exception/3 enables you to handle exceptions to a specific goal: on_exception(?ExceptionCode, +ProtectedGoal, +Handler) ProtectedGoal is executed. If all goes well, it will behave just as if you had written ProtectedGoal without the on_exception/3 wrapper. If an exception is raised while ProtectedGoal is running, Prolog will abandon ProtectedGoal entirely. Any bindings made by ProtectedGoal will be undone, just as if it had failed. 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 code raised with the ExceptionCode argument. If this unification succeeds, Handler will be executed as if you had written
ExceptionCode=<the actual exception code>,
Handler
If this unification fails, Prolog will keep searching up the ancestor list looking for another exception handler. If it reaches Prolog's top level (or a break level) without having found a call to on_exception/3 with a matching ExceptionCode, 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, then the call to on_exception/3 is also determinate. Setting up an exception handler with on_exception/3 is cheap provided that ProtectedGoal is an ordinary goal. Some efficiency is lost, in the current implementation, if ProtectedGoal is of the form (Goal1,Goal2) or (Goal1;Goal2). The ProtectedGoal is logically "inside" the on_exception/3 form, but the Handler is NOT. If an exception is raised inside the Handler, this on_exception/3 form will NOT be reactivated. If you want an exception handler which protects itself, you have to program it, perhaps like this:
recursive_on_exception_handler(Err, Goal, Handler) :-
on_exception(Err, Goal,
recursive_on_exception_handler(Err, Handler, Handler)).
Users can write a handler for the specific exception occurring when an undefined predicate is called by defining clauses for the hook predicate unknown_predicate_handler/3. This can be thought of as a "global" exception handler for this particular exception, because unlike on_exception/3, its effect is not limited to a particular goal. Furthermore, the exception is handled at the point where the undefined predicate is called. The handler can be written to apply to all unknown predicates, or to a class of them. The reference page contains an example of constraining the handler to certain predicates.
Exceptions raised by the Prolog system are called errors. The set of exception classes used by the system has been kept small. Here is a complete list:
The exception codes corresponding to these classes are:
instantiation_error(Goal, ArgNo)
type_error(Goal, ArgNo, TypeName, Culprit)
domain_error(Goal, ArgNo, DomainName, Culprit, Message)
range_error(Goal, ArgNo, TypeName, Culprit)
representation_error(Goal, ArgNo, Message)
existence_error(Goal, ArgNo, ObjectType, Culprit, Message)
permission_error(Goal, Operation, ObjectType, Culprit, Message)
context_error(Goal, ContextType, CommandType)
consistency_error(Goal, Culprit1, Culprit2, Message)
syntax_error(Goal, Position, Message, Left, Right)
resource_error(Goal, Resource, Message)
system_error(Message)
Most exception codes include a copy of the Goal which raised the exception. In general, built-in predicates which cause side-effects, such as the opening of a stream or asserting a clause into the Prolog database, attempt to do all error checking before the side-effect is performed. Unless otherwise indicated in the documentation for a particular predicate or error class, it should be assumed that goals which raise exceptions have not performed any side-effect.
An instantiation error occurs when a predicate or command is called with one of its input arguments insufficiently instantiated. The exception code associated with an instantiation error is
instantiation_error(Goal, ArgNo)
ArgNo is a non-negative integer indicating which argument caused the problem. ArgNo=0 means that the problem could not be localized to a single argument. Note that the ArgNoth argument of Goal might well be a non-variable: the error is in that argument. For example, the goal
X is Y+1
where Y is uninstantiated raises the exception
instantiation_error(_2298 is _2301+1,2)
because the second argument to is/2 contains a variable.
A type error occurs when an input argument is of the wrong "type". In general, a "type" is taken to be a class of terms for which there exists a unary "type test predicate". Some types are built-in, such as atom/1 and integer/1. Some are defined in library(types), such as chars/1. The type of a term is the sort of thing you can tell just by looking at it, without checking to see how "big" it is. So "integer" is a type, but "non-negative integer" is not, and "atom" is a type, but "atom with 5 letters in its name" and "atom starting with 'x'" are not. The point of a type error is that you have obviously passed the wrong sort of argument to a command; perhaps you have switched two arguments, or perhaps you have called the wrong predicate, but it isn't a subtle matter of being off by one. Most built-in predicates check all their input arguments for type errors. The exception code associated with a type error is
type_error(Goal, ArgNo, TypeName, Culprit)
For example, suppose we had a predicate
date_plus(NumberOfDays, Date0, Date)
true when Date0 and Date were date(Y,M,D) records and NumberOfDays was the number of days between those two dates. You might see an error term such as
type_error(/* Goal */ date_plus(27, date(18,mar,11), _235),
/* Argno */ 2,
/* TypeName */ integer,
/* Culprit */ mar
A domain error occurs when an input argument is of the right type but there is something wrong with its value. For example, the second argument to open/3 is supposed to be an atom which represents a valid mode for opening a file, such as 'read' or 'write'. If a number or a compound term is given instead, that is a type error. If an atom is given which is not a valid mode, that is a domain error. The main reason that we distinguish between type errors and domain errors is that they usually represent different sorts of mistake in your program. A type error usually indicates that you have passed the wrong argument to a command, whereas a domain error usually indicates that you passed the argument you meant to check, but you hadn't checked it enough. The exception code associated with a domain error is
domain_error(Goal, ArgNo, DomainName, Culprit, Message)
The arguments correspond to those of the exception code for a type error, except that DomainName is not in general the name of a unary predicate: it needn't even be an atom. For example, if some command requires an argument to be an integer in the range 1..99, it might use between(1,99) as the DomainName. With respect to the date_plus example under "Type Errors", if the month had been given as 13 it would have passed the type test but would raise a domain error. For example, the goal
open(somefile,rread,S)
raises the exception
domain_error(open(somefile,rread,_2490),2,'i/o mode',rread,'')
The Message argument is used to provide extra information about the problem. Some DomainNames recognized by the system include:
A range error occurs when an output argument was supplied with an illegal value. This is similar to a type error or a domain error, except that it is a hint that a variable would be a good thing to supply instead; type and domain errors are associated with input arguments, where a variable would usually not be a good idea. The exception code associated with a range error is
range_error(Goal, ArgNo, TypeName, Culprit)
This has the same arguments as a type error. Most built-in predicates do not raise any range errors. Instead they fail quietly when an output argument fails to unify.
A representation error occurs when your program calls for the computation of some well-defined value which cannot be represented. Most representation errors are some sort of overflow:
functor(T, f, 1000) % maximum arity is 255 X is 16'7fffffff, Y is X+1 % 32-bit signed integers atom_chars(X, L) % if length of L > 1024
are all representation errors. Floating-point overflow is a representation error. The exception code for a representation error is
representation_error(Goal, ArgNo, Message)
An existence error occurs when a predicate attempts to access something which does not exist. For example, trying to compile a file which does not exist, erasing a database reference which has already been erased. A less obvious example: reading past the end of file marker in a stream is regarded as asking for an object (the next character) which does not exist. The exception code associated with an existence error is
existence_error(Goal, ArgNo, ObjectType, Culprit, Message)
For example, see('../brother/niece') might raise the exception
existence_error(see('../brother/niece'),
1, file, '/usr/stella/parent/brother/niece',
errno(20))
where the Message encodes the UNIX error "ENOTDIR" (some component of the path is not a directory). As a general rule, if Culprit was provided in the goal as some sort of context-sensitive name, the Prolog system will try to resolve it to an absolute name, as shown here, so that you can see whether the problem is just that the name was resolved in the wrong context.
A permission error occurs when an operation is attempted which is among the kinds of operation which the system is in general capable of performing, and among the kinds which you are in general allowed to request, but this particular time it isn't permitted. Usually, the reason for a permission error is that the "owner" of one of the objects has requested that the object be protected. An example of this inside Prolog is attempting to change a predicate which has not been declared ":-dynamic". File system protection is the main cause of such errors. The exception code associated with a permission error is
permission_error(Goal, Operation, ObjectType, Culprit, Message)
A context error occurs when a goal or declaration appears in the wrong place. There may or may not be anything wrong with the goal or declaration as such; the point is that it is out of place. Calling :-multifile/1 as a goal is a context error, as is having :-module/2 anywhere but as the first term in a source file. The exception code associated with a context error is
context_error(Goal, ContextType, CommandType)
A consistency error occurs when two otherwise valid values or operations have been specified which are inconsistent with each other. For example, if two modules each import the same predicate from the other, that is a consistency error. The exception code associated with a consistency error is
consistency_error(Goal, Culprit1, Culprit2, Message)
A syntax error occurs when data are read from some external source but have an improper format or cannot be processed for some other reason. This category mainly applies to read/1 and its variants. The exception code associated with a syntax error is
syntax_error(Goal, Position, Message, Left, Right)
where Goal is the goal in question, Position identifies the position in the stream where reading started, and Message describes the error. Left and right are lists of tokens before and after the error, respectively. Note that the Position is where reading started, not where the error "is". read/1 does two things. First, it reads a sequence of characters from the current input stream up to and including a clause terminator, or the end of file marker, whichever comes first. Then it attempts to parse the sequence of characters as a Prolog term. If the parse is unsuccessful, a syntax error occurs. Thus, in the case of syntax errors, read/1 disobeys the normal rule that predicates should detect and report errors before they perform any side-effects, because the side-effect of reading the characters has been done. A syntax error does not necessarily cause an exception to be raised. The behavior can be controlled via a prolog_flag as follows:
A resource error occurs when some resource runs out. For example, you can run out of virtual memory, or you can exceed the operating system limit on the number of simultaneously open files. Often a resource error arises because of a programming mistake: for example, you may exceed the maximum number of open files because your program doesn't close files when it has finished with them. Or, you may run out of virtual memory because you have a non-terminating recursion in your program. The exception code for a resource error is
resource_error(Goal, Resource, Message)
System errors are problems that the operating system notices (or causes). Note that many of the exception indications returned by the operating system (such as "file does not exist") are mapped to Prolog exceptions; it is only really unexpected things which show up as system errors. The exception code for a system error is
system_error(Message)
where Message is not further specified.
Suppose you want a routine which is given a file name 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 which was opened. (There is no need to return the file name which 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(the atom 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.
The point of critical regions in your code is that sometimes you have data-base updates or other operations which, once started, must be completed in order to avoid an inconsistent state. In particular, such operations should not be interrupted by a ^C from the keyboard. In releases of Quintus Prolog prior to release 3.0, library(critical) was provided to allow critical regions to be specified using the predicates begin_critical/0 and end_critical/0. These predicates are still provided, but they should be regarded as obsolete since they do not interact well with exception handling. An exception occurring in between the begin_critical and the end_critical could cause two problems:
To avoid these problems, you should use the new predicates
critical(Goal) critical_on_exception(ErrorCode, Goal, Handler)
which library(critical) now provides. critical/1 runs the specified goal inside a critical region. It solves (2) by catching any exceptions that are raised and taking care to close the critical region before re-raising the exception. critical_on_exception/3 allows you to solve (1) by specifying appropriate clean-up actions in Handler. If an exception occurs during Goal, and the exception code unifies with ErrorCode, critical_on_exception/3 acts as if you had written
critical(Handler)
instead. That is, the Handler will still be inside the critical region, and only the first solution it returns will be taken. These forms also have the effect of committing to the first solution of Goal. Since the point of a critical region is to ensure that some operation with side-effects is completed, Goal should be determinate anyway, so this should be no problem.
contact: product
support sales information