From: Will Hartung
Subject: handlers, restarts, and conditions -- Oh My!
Date: 
Message-ID: <3c8f946a$1_6@news.teranews.com>
I'm not grokking conditions and restarts very well. Or, perhaps the higher
level structures.

Given this simple framework:

(defvar *table* (make-hash-table))

(defun add-thing (symbol)
  (when (gethash symbol *table*)
      (cerror "Redefine item ~A." "~A is already defined." symbol))
  (setf (gethash symbol *table*) symbol))

(defun add-all (list)
  (dolist (i list)
    (add-thing i)))

Here are my questions.

How do I wrap either add-thing or add-all so that the restart is
automatically taken, without hitting the debugger?

Something like : (redefine-it-anyway (add-all '(a b c))

I tried making my own condition and playing with handler-bind, but the
debugger was still firing.

By the same token, rather than taking the restart, how do I capture but
ignore the restart? I'm guessing I would have to do that within the add-all
function itself, or can it be done externally as well?

The examples in the Hyperspec aren't really clear.

And finally, how can I put information in the condition to be passed to the
handler?

So that I could end up writing a function like:

CL-USER 1 >(define-all-damnit '(a b c d))
A redefined
C redefined
NIL

CL-USER 2 >

Finally, when I was playing with this all, I had something like:
(add-thing 'a)
(add-thing 'b)
(add-thing 'c)

in a file. When I loaded the file, and had 'b already defined, I got TWO
restarts available to me, the first one was from my code, to redefine 'b,
and the other was from LOAD asking to reload the entire file. How are the
conditions stacked up? That was pretty magic behavior.

Thanx for any help you can provide!

Regards,

Will Hartung
(·····@msoft.com)

From: Kenny Tilton
Subject: Re: handlers, restarts, and conditions -- Oh My!
Date: 
Message-ID: <3C8FCF4E.1A13A568@nyc.rr.com>
1. Check out define-condition

2. Consider if you want to have another parameter for ADD-THING, ala:

(defun add-thing (symbol &key damnit!)
    (if (gethash...)
       (unless damnit!
          (make-condition 'doh! :symbol symbol))
       (setf (gethash...))))


-- 

 kenny tilton
 clinisys, inc
 ---------------------------------------------------------------
 "Be the ball...be the ball...you're not being the ball, Danny."
                                               - Ty, Caddy Shack
From: Pierre R. Mai
Subject: Re: handlers, restarts, and conditions -- Oh My!
Date: 
Message-ID: <87pu28ujq0.fsf@orion.bln.pmsf.de>
"Will Hartung" <·····@msoft.com> writes:

> (defvar *table* (make-hash-table))
> 
> (defun add-thing (symbol)
>   (when (gethash symbol *table*)
>       (cerror "Redefine item ~A." "~A is already defined." symbol))
>   (setf (gethash symbol *table*) symbol))
> 
> (defun add-all (list)
>   (dolist (i list)
>     (add-thing i)))
> 
> Here are my questions.
> 
> How do I wrap either add-thing or add-all so that the restart is
> automatically taken, without hitting the debugger?
> 
> Something like : (redefine-it-anyway (add-all '(a b c))

(defmacro with-full-speed-ahead (&body body)
  "This macro will establish a handler for error, that invokes any
existing continue restart for the given error, if it exists.
Otherwise the handler will decline to handle the condition."
  `(handler-bind ((error #'continue)) ,@body))

This relies on the predefined ANSI CL function continue, which invokes
the corresponding continue restart if it exists, or returns nil
otherwise.

Of course in reality, you'd want to define a more specialized
condition with define-condition, and only handle those conditions,
lest you invoke a continue restart on a trying-to-kill-user-error.

> I tried making my own condition and playing with handler-bind, but the
> debugger was still firing.

Handler-bind needs to do a non-local transfer of control in order to
prevent the default handler (ultimately the debugger for error
conditions) from firing.  Invoking a restart is one such transfer of
control.

> By the same token, rather than taking the restart, how do I capture but
> ignore the restart? I'm guessing I would have to do that within the add-all
> function itself, or can it be done externally as well?

What are you trying to achieve?  Ignoring a restart just means not
invoking it, so you only have to not do that.  What would you do with
a "captured" restart?

> And finally, how can I put information in the condition to be passed to the
> handler?

Just define slots in your condition class, and fill them when creating
the condition object, e.g.

(define-condition redefinition-error (error)
  ((symbol :initarg :symbol :reader redefinition-error-symbol))
  (:report
   (lambda (c s)
     (format s "Trying to redefine ~S which is already defined."
             (redefinition-error-symbol c)))))

(defun add-thing (symbol)
  (when (gethash symbol *table*)
    (cerror "Redefine symbol ~*~S." 'redefinition-error :symbol symbol))
  (setf (gethash symbol *table*) symbol))

(defun handle-redefinition-error (condition)
  (unless (member (redefinition-error-symbol condition) '(t nil))
    (continue condition)))

(handler-bind ((redefinition-error #'handle-redefinition-error))
  (add-thing 'a)
  (add-thing 't)
  (add-thing 'a)
  (add-thing 't))

> So that I could end up writing a function like:
> 
> CL-USER 1 >(define-all-damnit '(a b c d))
> A redefined
> C redefined
> NIL

(defun define-all-damit (list)
  (handler-bind ((redefinition-error #'continue)) (add-all list)))

> Finally, when I was playing with this all, I had something like:
> (add-thing 'a)
> (add-thing 'b)
> (add-thing 'c)
> 
> in a file. When I loaded the file, and had 'b already defined, I got TWO
> restarts available to me, the first one was from my code, to redefine 'b,
> and the other was from LOAD asking to reload the entire file. How are the
> conditions stacked up? That was pretty magic behavior.

There is only one condition, namely your simple-error condition.  But
there can be many restarts that are applicable.  Restarts are
precanned ways to proceed in order to "correct" the error situation.
They can be established at many points in the program.  In your
example the file loader establishes a restart that allows the user to
reload the whole file.  You established a continue restart that just
returns nil from the cerror form, and hence proceeds with
redefinition.

It is up to condition handlers (including ultimately the human user
via the debugger, if no intervening handler decides to handle the
condition) to choose one of the available restarts, or to "correct"
the error situation in some other way.

Hope this helps...

Regs, Pierre.

-- 
Pierre R. Mai <····@acm.org>                    http://www.pmsf.de/pmai/
 The most likely way for the world to be destroyed, most experts agree,
 is by accident. That's where we come in; we're computer professionals.
 We cause accidents.                           -- Nathaniel Borenstein