18
Error Handling Common Lisp has the throw/catch statements so that you can simulate exception handling as seen in languages like Java But CL goes far beyond this by providing a number of alternative mechanisms you can use throw/catch or return-from but include an unwind- protect so that specific operations happen no matter if you branch or not you can specify an error that terminates the execution of the program you can specify an error that drops you in the debugger you can specify an error that drops you in the debugger and includes a specialized restart you can define conditions (same as Exceptions in Java) and then there are several ways to handle conditions including specialized restarts Why all the different approaches? in part, the CL philosophy is one of giving you as many tools as possible however, CL differs from most languages in that you have the interactive environment, so using the debugger becomes another weapon in your programming arsenal the idea of branching to an exception handler, as performed in Java, is both limiting and may lead to continuation problems, CL tries to do better

Error Handling

Embed Size (px)

DESCRIPTION

Error Handling. Common Lisp has the throw/catch statements so that you can simulate exception handling as seen in languages like Java But CL goes far beyond this by providing a number of alternative mechanisms - PowerPoint PPT Presentation

Citation preview

Page 1: Error Handling

Error Handling• Common Lisp has the throw/catch statements so that you can

simulate exception handling as seen in languages like Java• But CL goes far beyond this by providing a number of

alternative mechanisms– you can use throw/catch or return-from but include an unwind-protect so

that specific operations happen no matter if you branch or not – you can specify an error that terminates the execution of the program– you can specify an error that drops you in the debugger– you can specify an error that drops you in the debugger and includes a

specialized restart– you can define conditions (same as Exceptions in Java) and then there are

several ways to handle conditions including specialized restarts

• Why all the different approaches? – in part, the CL philosophy is one of giving you as many tools as possible

• however, CL differs from most languages in that you have the interactive environment, so using the debugger becomes another weapon in your programming arsenal

– the idea of branching to an exception handler, as performed in Java, is both limiting and may lead to continuation problems, CL tries to do better

Page 2: Error Handling

Catch/Throw vs. Return-from• Throw can be used to transfer control to the most recent

catch statement that has a matching symbol– this allows you to escape from a potentially erroneous situation

but has two problems• throw unwinds the stack to the point where the catch is found• finding the proper catch is done dynamically, so there is no way to

necessarily know where control will transfer when writing the code

– return-from is preferred because it can be used• to terminate the function and return control to the calling unit

• In either case, any remaining instructions in the function or block are ignored– we may want to force those instructions to execute (such as a

close statement) and so we would use unwind-protect to surround the code with the error and the clean-up code

Page 3: Error Handling

Unwind Protect• Unwind-protect executes the next instruction and then

guarantees that code that follows the next instruction inside the unwind-protect will execute no matter if the instruction tries to leave the block through some branch – such as through a throw or return-from

• typically the next instruction will be a function call or a progn statement that permits multiple operations

• This will be helpful when some concluding statement(s) should be assured of running, such as closing a file

• Form: (unwind-protect (progn

;; statements go here ) (;; statements here will automatically be executed) (;; and more statements will be executed) (;; and so on))

Page 4: Error Handling

Example(defun fn-a ( )

(catch 'fn-a (print 'before-fn-b-call) (fn-b)

(print 'after-fn-b-call)))

(defun fn-b ( ) (print 'before-fn-c-call) (fn-c) (print 'after-fn-c-call))

(defun fn-c ( ) (print 'before-throw) (throw 'fn-a 'done) (print 'after-throw))

If we call fn-a, we get:BEFORE-FN-B-CALL BEFORE-FN-C-CALL BEFORE-THROW DONE

If we change fn-b to be(defun fn-b ( ) (unwind-protect (progn

(print 'before-fn-c-call) (fn-c))

(print 'after-fn-c-call)))

We get BEFORE-FN-B-CALL BEFORE-FN-C-CALL BEFORE-THROW AFTER-FN-C-CALL DONE

Page 5: Error Handling

Another Example(defun compute-average (filename) (let (infile (count 0) (sum 0) avg (status t))

(unwind-protect (progn

(setf infile (open filename))(setf status (do ((temp (read infile nil nil)

(read infile nil nil))) ((null temp) temp) (setf count (+ count 1))

(setf sum (+ sum temp)))))(if (null status)

(if (> count 0) (setf avg (/ sum count)) (setf avg (format nil "File ~A empty" filename))) (setf avg (format nil "Error in File ~A occurred" filename)))

(close infile)) avg))

We will exit this loop prematurely if either there is an error reading the file or the file is not found

Status gets temp, whateverthe last thing was that wasread in – it should be nil

Status is then used in theprotect portion

Page 6: Error Handling

Error• The error instruction causes a program to terminate at that point

and return an error message– you can provide a tailored error message for the given circumstance

• your output statement is like a format in that it can contain various ~ arguments followed by variables

– since this instruction terminates the program, a continuation/restart is not possible – you are dropped in the debugger with abort options only

• Examples:(defun reciprocal (x)

(if (= x 0) (error "Cannot divide by 0") (/ 1 x)))

(defun count0 (lis) (let ((num 0))

(dolist (a lis) (if (not (numberp a))

(error "List contains non-numeric item ~A" a) (if (= a 0) (setf num (+ num 1)))))

num))

(reciprocal 0)

Error: Cannot divide by 0 1 (abort) Return to level 0. 2 Return to top loop level 0.

Page 7: Error Handling

Cerror• cerror is a superior version

which also drops you in the debugger, but unlike error (or break), cerror – gives you the ability to specify

your own continuation message (if desired) so that you can restart the program from this point

– note that the restart just goes on to the next instruction

• Form: (cerror “restart message” “error message” params)– the parameters refer to any ~var

items in either message– continuation must be part of the

code, that is, you cannot provide a list of possible restarts, instead it just continues

(defun count0 (lis) (let ((num 0))

(dolist (a lis) (if (not (numberp a))

(cerror "Skip ~A and continue" "List contains non-numeric item ~A" a)

(if (= a 0) (setf num (+ num 1))))) num))

CL-USER 86 > (count0 '(1 0 a 3 0 4))Error: List contains non-numeric item A 1 (continue) Skip A and continue 2 (abort) Return to level 0. 3 Return to top loop level 0.CL-USER 87 : 1 > :c 12

Page 8: Error Handling

Assertions• An assertion is similar to cerror except that you can permit the

user to submit replacement values • Form: (assert test (value(s)) &optional “message”)

– the message is like what we had in error or cerror, without it, the message is not necessarily useful for a non-programmer

• If the test fails, then CL drops into the debugger and the user has a chance to enter a new value (values) as specified in the list– examples:

(defun reciprocal (x) (assert (not (= 0 x)) (x)) (/ 1 x))

CL-USER 51 > (reciprocal 0)Error: The assertion (NOT (= 0 X)) failed. 1 (continue) Retry assertion with new value for X. 2 (abort) Return to level 0. 3 Return to top loop level 0.

(defun reciprocal (x) (assert (not (= 0 x)) (x)“Cannot divide by 0, replace x”) (/ 1 x))

The list can containmultiple variables inwhich case the user isasked to input newvalues for all of them

Page 9: Error Handling

Assertion Example• Consider as an example that we want to take two

values and ensure that they are non-negative numbers• Here is a function to accomplish this:

(defun test-values (a b)(assert (and (numberp a) (numberp b)) (a b))(assert (and (>= a 0) (>= b 0)) (a b))

(list a b))

(test-values -5 'a)Error: The assertion (AND (NUMBERP A) (NUMBERP B)) failed. 1 (continue) Retry assertion with new values for A, B. 2 (abort) Return to level 0. 3 Return to top loop level 0.CL-USER 3 : 1 > :c 1Enter a form to be evaluated: [pop-up window asks to replace a] -5Enter a form to be evaluated: [pop-up window asks to replace b] -4

Error: The assertion (AND (>= A 0) (>= B 0)) failed. 1 (continue) Retry assertion with new values for A, B. 2 (abort) Return to level 0. 3 Return to top loop level 0.CL-USER 4 : 1 > :c 1Enter a form to be evaluated:[pop-up window for a] 5Enter a form to be evaluated:[pop-up window for b] 4

(5 4)

Page 10: Error Handling

Defining Conditions• As in Java, conditions in CL are objects

– You define a class for a specific type of condition– Condition objects include slots like with objects

• slots can include arguments like initarg, initform, accessor, etc– Conditions can inherit from other conditions– Aside from slots, a condition can also include a

• :report clause which will be used as output when the condition is raised• :documentation clause

(define-condition my-error (condition)((error-type :initarg :error-type

:initform "Unknown" :accessor error-type) (error-location :initarg :error-location

:initform "Unknown" :accessor error-location))(:report (lambda (condition stream)

(format stream "Condition ~A arose in ~A location" (error-type condition) (error-location condition)))))

(error 'foo :error-type "Mistake") would generate the messageCondition Mistake arose in Unknown location

Page 11: Error Handling

Handling the Condition• As seen with the previous example, the condition can be raised

explicitly through an error message– this drops the program into the debugger, but the user’s only restart choice

is to abort the program• The condition can also be raised through cerror, where the

programmer can specify what restarting will do– here is a revised reciprocal function that will raise the my-error condition

and still let the user continue(defun reciprocal (x) (when (= x 0)

(cerror "Use 1 in place of 0 for input" 'my-error :error-type "Division by zero" :error-location "in function reciprocal") (setf x 1))

(/ 1 x)) CL-USER 89 > (reciprocal 0)Condition Division by zero arose in in function reciprocal location 1 (continue) Use 1 in place of 0 for input 2 (abort) Return to level 0. 3 Return to top loop level 0.CL-USER 90 : 1 > :c 11

Page 12: Error Handling

More Elaborate Continuation• The previous example forced the user to accept x = 1 if they

wanted to continue– Can we improve on this?– The cerror command only allows for 1 continuation, but we can be more

elaborate in what happens with that continuation code

(defun reciprocal (x) (when (= x 0)

(cerror "Input a new value in place of 0" 'my-error :error-type "Division by zero" :error-location "in function reciprocal") (format t "~%Enter new value: ") (setf x (read)))

(/ 1 x)) CL-USER 57: > (reciprocal 0)Condition Division by zero arose in in function reciprocal location 1 (continue) Input a new value in place of 0 2 (abort) Return to level 0. 3 Return to top loop level 0.CL-USER 58 : 1 > :c 1Enter new value: 51/5

Page 13: Error Handling

Condition Handlers• The standard form of a condition handler is through the

handler-bind macro– we are used to seeing try-catch blocks in something of this form

in Java (C++ is similar):• { … try {…} catch(ExceptionType e) {…}

– in Common Lisp, the catch is made through handler-bind, and the try block is embedded into this block of code

– (handler-bind ((condition-name #’(lambda (x) ;; code goes here to handle the condition)))

;; code goes here equivalent to the try block)

– the code after the ))) will can contain signal statements that can signal the given condition, in which case control is transferred to that chunk of code

– to signal a condition: (signal ’condition-name)• the signal instruction can also include accessor or initarg arguments such

as (signal ’my-error :error-type “Help I’m lossed” :error-location “my-function”)

Page 14: Error Handling

Example

(defun my-divider (a b) (if (and (= a 0) (= b 0))

(signal 'my-error :error-type "0 / 0 Does not exist" :error-location "my-divider")

(if (= b 0) (signal 'my-error :error-type "Division by zero" :error-location "my-divider")

(/ a b))))

(defun safe-divider (a b) (handler-bind ((my-error #'(lambda (x)

(format t "~A. How should we proceed?~%1. Return 0.~%2. Input new value for denominator.~%3. Input new values for numerator and denominator.~%Select: " x)

(let ((temp (read))) (cond ((= temp 1) (return-from safe-divider 0))

((= temp 2) (format t "Enter new denominator: ") (setf b (read))(return-from safe-divider (my-divider a b)))

((= temp 3) (format t "Enter new numerator: ") (setf a (read)) (format t "Enter new denominator: ") (setf b (read)) (return-from safe-divider (my-divider a b)))

(t (return-from safe-divider nil)))))))(my-divider a b)))

Page 15: Error Handling

How This Differs From Java• In a language like Java, an exception handler

– contains the mechanism for handling the exception– its location dictates continuation

• for instance, consider that method handle1 has a try/catch block that can catch Exception Foo

– handle1 calls method handle2, which throws Foo– handle2 calls error3 which throws Foo– if a Foo exception arises in handle2, handle2 transfers control back to

handle1 to solve the problem, handle1 then continues – this is what we might expect

– if a Foo exception arises in error3 then error3 throws to handle2 which throws to handle1

– should handle2 have been aborted because of an exception in error3?

– in Common Lisp, since the handler was part of the handler-bind, restart will (or can) occur in the instruction after the call that produced the signal

• in our previous example, this was not the case because of the use of return-from statements

Page 16: Error Handling

Handler-Case• Handler-bind allows you to handle a situation without

removing items from the stack– But unfortunately, it is somewhat complex to use

• Handler-case is a simpler condition handler– Form:

• (handler-case (;; code here that might cause a condition) (condition1 (param) ;;condition1 handler) (condition2 (param) ;; condition2 handler) …)

– Example:

(handler-case (progn

(print (/ b a)) (print (/ b c)))

(division-by-zero (x) (print 'oops)))

Here assume that a, b and c arevariables equal to different values

If a or c are 0, then control transfersto the error handler

Notice that conditions can be built-in or user-defined

Page 17: Error Handling

Restart Handlers• Handler-case unwinds the stack to the point where the

handler-case is found– so this is of less use than handler-bind

• Restart-case is a version of handler-case does not try to handle the condition but instead – drops you in the debugger– however, the restart-case allows you to specify restart options

that the user can select between• since we generally want to shelter a user from the debugger, we may

not want to handle conditions in this way

• A variation is invoke-restart – when provided with a function name as a symbol, it will find

the most recently bound version of that restart (that is, the one nearest on the run-time stack) and resume execution

• we are going to skip the details of the restart forms since they get more and more complicated!

Page 18: Error Handling

And Finally…• Using signal outside of a handler macro will result in the

condition being raised but not handled, so you will just get a nil return value– Error and Cerror call signal for you by the way

• Warn is like error or cerror in that it signals the condition, but rather than being dropped in the debugger, if signal returns then execution continues from that point – this allows you to perform the operation of a condition handler

without having to worry about restarts

• A built-in restart is called continue, which merely resumes (this would be equivalent to using warn instead of error or cerror)– the Practical Common Lisp on-line text has more detail, read it

if you are interested!