From: Tamas Papp
Subject: flexible error handling
Date: 
Message-ID: <871wdt55kb.fsf@pu100877.student.princeton.edu>
I have a function that plots the graph of a given univariate function.
Sometimes it would be convenient to handle errors generated by this
function gracefully, eg if the function encounters a division by zero,
I would just leave that part of the plot blank.

In order to produce a toy example, the code below just prints the
values of f evaluated at integers from 0 to 10, instead of plotting.

(defun 0-to-10 (f)
  (dotimes (i 10)
    (format t "~a "
	    (handler-case (funcall f i)
	      (division-by-zero () nil))))
  (terpri))

(0-to-10 #'-)   ; prints 0 -1 -2 -3 -4 -5 -6 -7 -8 -9 
(0-to-10 #'/)   ; prints NIL 1 1/2 1/3 1/4 1/5 1/6 1/7 1/8 1/9 

Instead of hardcoding which errors to accept, I would like to allow
the user to specify a list of errors which are OK.  I would like to
implement something that behaves like this:

(defun 0-to-10 (f &optional (allowed-errors nil))
   ...)

(0-to-10 #'(lambda (x)
	     (if (= 4 x)
		 (error 'I-just-dont-like-4)
		 (/ x)))
	 '(division-by-zero I-just-dont-like-4)) 
			; prints NIL 1 1/2 1/3 NIL 1/5 1/6 1/7 1/8 1/9 

Thanks,

Tamas

From: Pillsy
Subject: Re: flexible error handling
Date: 
Message-ID: <1187974211.670279.234660@i13g2000prf.googlegroups.com>
On Aug 24, 11:13 am, Tamas Papp <······@gmail.com> wrote:

> I have a function that plots the graph of a given univariate function.
> Sometimes it would be convenient to handle errors generated by this
> function gracefully, eg if the function encounters a division by zero,
> I would just leave that part of the plot blank.

> In order to produce a toy example, the code below just prints the
> values of f evaluated at integers from 0 to 10, instead of plotting.

> (defun 0-to-10 (f)
>   (dotimes (i 10)
>     (format t "~a "
>             (handler-case (funcall f i)
>               (division-by-zero () nil))))
>   (terpri))

> (0-to-10 #'-)   ; prints 0 -1 -2 -3 -4 -5 -6 -7 -8 -9
> (0-to-10 #'/)   ; prints NIL 1 1/2 1/3 1/4 1/5 1/6 1/7 1/8 1/9

> Instead of hardcoding which errors to accept, I would like to allow
> the user to specify a list of errors which are OK.

I can think of a couple ways to do this. One just involves a really
minimal set-up like this:

(defun call-with-handler (thunk handler)
  (handler-case (funcall thunk)
    (condition (cond)
      (funcall handler cond))))

Then the user can define a generic function that handles the errors
the way they want:

(defgeneric handler (condition)
  (:method ((condition error))
    (error "Don't know how to handle a ~S error!" condition))
  (:method ((condition division-by-zero))
    nil))

(defun 0-to-10 (f)
  (dotimes (i 10)
    (format t "~a " (call-with-handler (lambda ()
					(funcall f i))
				      #'handler)))
  (terpri))

This means:

(0-to-10 #'+) ;; prints 0 1 2 3 4 5 6 7 8 9
(0-to-10 #'/) ;; prints nil 1 1/2 1/3 1/4 1/5 1/6 1/7 1/8 1/9
(0-to-10 #'(lambda (i) (error "foo! ~S" i)) ;; Drops to debugger.

This strikes me as a nice and elegant way to do things. OTOH, it may
not be what you want because CL doesn't have a standard way (that I
know of) of defining an anonymous generic function, and you can't just
set up a dynamic binding that establishes how to handle errors. In
that case, you can do it in an uglier way, like so:

(defun call-with-handled-errors (thunk handler-alist)
  (handler-case (funcall thunk)
    (error (err)
      (let ((handler (cdr (assoc (class-name (class-of err))
				 handler-alist))))
	(if handler
	    (funcall handler err)
	    (error "Don't know how to handle error ~S!" err))))))

(defvar *ignored-errors* (list (cons 'division-by-zero (constantly
nil))))

(defun another-0-to-10 (f)
  (dotimes (i 10)
    (format t "~a " (call-with-handled-errors (lambda ()
						(funcall f i))
					      *ignored-errors*)))
  (terpri))

(another-0-to-10 #'+) ;; prints 0 1 2 3 4 5 6 7 8 9
(another-0-to-10 #'/) ;; prints nil 1 1/2 1/3 1/4 1/5 1/6 1/7 1/8 1/9
(another-0-to-10 #'(lambda (i) (error "foo! ~S" i)) ;; Drops to
debugger.

I've only done the barest minimum of testing, but I think these should
serve. You can paper over them with prettier macros easily, if you
like.

Cheers,
Pillsy
From: Marco Antoniotti
Subject: Re: flexible error handling
Date: 
Message-ID: <1187971627.387490.179320@i38g2000prf.googlegroups.com>
On Aug 24, 11:13 am, Tamas Papp <······@gmail.com> wrote:
> I have a function that plots the graph of a given univariate function.
> Sometimes it would be convenient to handle errors generated by this
> function gracefully, eg if the function encounters a division by zero,
> I would just leave that part of the plot blank.
>
> In order to produce a toy example, the code below just prints the
> values of f evaluated at integers from 0 to 10, instead of plotting.
>
> (defun 0-to-10 (f)
>   (dotimes (i 10)
>     (format t "~a "
>             (handler-case (funcall f i)
>               (division-by-zero () nil))))
>   (terpri))
>
> (0-to-10 #'-)   ; prints 0 -1 -2 -3 -4 -5 -6 -7 -8 -9
> (0-to-10 #'/)   ; prints NIL 1 1/2 1/3 1/4 1/5 1/6 1/7 1/8 1/9
>
> Instead of hardcoding which errors to accept, I would like to allow
> the user to specify a list of errors which are OK.  I would like to
> implement something that behaves like this:
>
> (defun 0-to-10 (f &optional (allowed-errors nil))
>    ...)
>
> (0-to-10 #'(lambda (x)
>              (if (= 4 x)
>                  (error 'I-just-dont-like-4)
>                  (/ x)))
>          '(division-by-zero I-just-dont-like-4))
>                         ; prints NIL 1 1/2 1/3 NIL 1/5 1/6 1/7 1/8 1/9
>
> Thanks,
>
> Tamas

Would the following work? (Untested: something along these lines may
work and generalizations are possible)

(defun 0-to-10 (f &rest ignored-errors)
  (macrolet ((call-f-with-ignored-errors ()
                `(handler-case (funcall f)
                     ,(loop for ie in ',ignored-errors
                            collect (list ie (%%%ie%%%) (declare
(ignore %%%ie%%%))))
                     )))
      (call-f-with-ignored-errors))

Cheers

Marco
From: Pascal Bourguignon
Subject: Re: flexible error handling
Date: 
Message-ID: <874piozya6.fsf@thalassa.informatimago.com>
Marco Antoniotti <·······@gmail.com> writes:
> Would the following work? (Untested: something along these lines may
> work and generalizations are possible)
>
> (defun 0-to-10 (f &rest ignored-errors)
>   (macrolet ((call-f-with-ignored-errors ()
>                 `(handler-case (funcall f)
>                      ,(loop for ie in ',ignored-errors
>                             collect (list ie (%%%ie%%%) (declare
> (ignore %%%ie%%%))))
>                      )))
>       (call-f-with-ignored-errors))

Cannot work, because macros are expanded long BEFORE run-time, when
ignore-errors are eventually known.

-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
Our enemies are innovative and resourceful, and so are we. They never
stop thinking about new ways to harm our country and our people, and
neither do we. -- Georges W. Bush
From: Marco Antoniotti
Subject: Re: flexible error handling
Date: 
Message-ID: <1187982921.376991.123020@x35g2000prf.googlegroups.com>
Right.

Your version is better.  I guess I'd like a version of IGNORE-ERRORS
like

(defmacro ignore-some-errors (error-list &body forms) ...)

Cheers

Marco

On Aug 24, 12:34 pm, Pascal Bourguignon <····@informatimago.com>
wrote:
> Marco Antoniotti <·······@gmail.com> writes:
> > Would the following work? (Untested: something along these lines may
> > work and generalizations are possible)
>
> > (defun 0-to-10 (f &rest ignored-errors)
> >   (macrolet ((call-f-with-ignored-errors ()
> >                 `(handler-case (funcall f)
> >                      ,(loop for ie in ',ignored-errors
> >                             collect (list ie (%%%ie%%%) (declare
> > (ignore %%%ie%%%))))
> >                      )))
> >       (call-f-with-ignored-errors))
>
> Cannot work, because macros are expanded long BEFORE run-time, when
> ignore-errors are eventually known.
>
> --
> __Pascal Bourguignon__                    http://www.informatimago.com/
> Our enemies are innovative and resourceful, and so are we. They never
> stop thinking about new ways to harm our country and our people, and
> neither do we. -- Georges W. Bush
From: David Lichteblau
Subject: Re: flexible error handling
Date: 
Message-ID: <slrnfcu1v8.ead.usenet-2006@radon.home.lichteblau.com>
On 2007-08-24, Tamas Papp <······@gmail.com> wrote:
> Instead of hardcoding which errors to accept, I would like to allow
> the user to specify a list of errors which are OK.  I would like to
> implement something that behaves like this:

(defun ignoring-errors-of-type (fn type)
  (block nil
    (handler-bind ((condition (lambda (c) (when (typep c type) (return c)))))
      (funcall fn))))

(ignoring-errors-of-type (lambda () (/ 0)) '(or division-by-zero other-error))
From: Thomas F. Burdick
Subject: Re: flexible error handling
Date: 
Message-ID: <1188057341.716709.25190@i13g2000prf.googlegroups.com>
On Aug 24, 6:31 pm, David Lichteblau <···········@lichteblau.com>
wrote:
> On 2007-08-24, Tamas Papp <······@gmail.com> wrote:
>
> > Instead of hardcoding which errors to accept, I would like to allow
> > the user to specify a list of errors which are OK.  I would like to
> > implement something that behaves like this:
>
> (defun ignoring-errors-of-type (fn type)
>   (block nil
>     (handler-bind ((condition (lambda (c) (when (typep c type) (return c)))))
>       (funcall fn))))
>
> (ignoring-errors-of-type (lambda () (/ 0)) '(or division-by-zero other-error))

This solution is pretty good -- although I'd handle it a bit
differently (see below) -- but more importantly it is the only
suggestion so far that's not disasterously bad.  The others either
won't work or will unwind your stack at totally inappropriate moments!

My only differences with David's suggestion here are purely stylistic:

(defun call-ignoring-error-types (thunk types &optional default-value)
  (labels ((handler (c)
             (when (find c types :test #'typep)
               (return-from call-ignoring-error-types default-
value))))
    (handler-bind ((serious-condition #'handler))
      (funcall thunk))))
From: Tamas Papp
Subject: Re: flexible error handling
Date: 
Message-ID: <87ps1b4mgz.fsf@pu100877.student.princeton.edu>
"Thomas F. Burdick" <········@gmail.com> writes:

> On Aug 24, 6:31 pm, David Lichteblau <···········@lichteblau.com>
> wrote:
>> On 2007-08-24, Tamas Papp <······@gmail.com> wrote:
>>
>> > Instead of hardcoding which errors to accept, I would like to allow
>> > the user to specify a list of errors which are OK.  I would like to
>> > implement something that behaves like this:
>>
>> (defun ignoring-errors-of-type (fn type)
>>   (block nil
>>     (handler-bind ((condition (lambda (c) (when (typep c type) (return c)))))
>>       (funcall fn))))
>>
>> (ignoring-errors-of-type (lambda () (/ 0)) '(or division-by-zero other-error))
>
> This solution is pretty good -- although I'd handle it a bit
> differently (see below) -- but more importantly it is the only
> suggestion so far that's not disasterously bad.  The others either
> won't work or will unwind your stack at totally inappropriate moments!

I used Pascal's solution, what do you think is wrong with it?

Thanks,

Tamas
From: Thomas F. Burdick
Subject: Re: flexible error handling
Date: 
Message-ID: <1189096618.197898.75330@50g2000hsm.googlegroups.com>
[reviving this thread because I just remembered it]

On Aug 25, 6:18 pm, Tamas Papp <······@gmail.com> wrote:
> "Thomas F. Burdick" <········@gmail.com> writes:
>
> > This solution is pretty good -- although I'd handle it a bit
> > differently (see below) -- but more importantly it is the only
> > suggestion so far that's not disasterously bad.  The others either
> > won't work or will unwind your stack at totally inappropriate moments!
>
> I used Pascal's solution, what do you think is wrong with it?

It's crap?  But seriously, how it works is it handles every condition,
unwinds the stack, *then* decides whether it should handle the
condition that it has *already* handled, and if it shouldn't have, it
*re*-signals the condition with error.  First of all, CL:ERROR is a
pretty poor way to signal a non-error condition, eg, a warning.
Second, and most importantly, the stack has been unwound, you've lost
your dynamic context.  If there was a restart you wanted to invoke,
tough.  Try using the two solutions with, for example, these
functions:

  (defun foo (x)
    (when (floatp x)
      (warn "I see a float: ~F" x))
    (* x x))

  (defun bar (x)
    (if (and (rationalp x)
             (not (integerp x)))
        (progn (cerror "Turn it into a single-float"
                       "Got a fraction: ~A" x)
               (* x 1.0s0))
        x))

It's the type of mistake one makes when new to CL (or just its
condition system).  Suggesting it as a solution to a newbie is really
doing them a disservice.
From: Pascal Bourguignon
Subject: Re: flexible error handling
Date: 
Message-ID: <878x80zyle.fsf@thalassa.informatimago.com>
Tamas Papp <······@gmail.com> writes:

> I have a function that plots the graph of a given univariate function.
> Sometimes it would be convenient to handle errors generated by this
> function gracefully, eg if the function encounters a division by zero,
> I would just leave that part of the plot blank.
>
> In order to produce a toy example, the code below just prints the
> values of f evaluated at integers from 0 to 10, instead of plotting.
>
> (defun 0-to-10 (f)
>   (dotimes (i 10)
>     (format t "~a "
> 	    (handler-case (funcall f i)
> 	      (division-by-zero () nil))))
>   (terpri))
>
> (0-to-10 #'-)   ; prints 0 -1 -2 -3 -4 -5 -6 -7 -8 -9 
> (0-to-10 #'/)   ; prints NIL 1 1/2 1/3 1/4 1/5 1/6 1/7 1/8 1/9 
>
> Instead of hardcoding which errors to accept, I would like to allow
> the user to specify a list of errors which are OK.  I would like to
> implement something that behaves like this:
>
> (defun 0-to-10 (f &optional (allowed-errors nil))
>    ...)
>
> (0-to-10 #'(lambda (x)
> 	     (if (= 4 x)
> 		 (error 'I-just-dont-like-4)
> 		 (/ x)))
> 	 '(division-by-zero I-just-dont-like-4)) 
> 			; prints NIL 1 1/2 1/3 NIL 1/5 1/6 1/7 1/8 1/9 

(define-condition  I-just-dont-like-4 (error) ()) 

(defun 0-to-10 (f ignorable-conditions)
   (dotimes (i 10)
      (format t "~A "
        (handler-case (funcall f i)
        (t (condition) 
           (if (member condition ignorable-conditions :test (function typep))
              nil
              (error condition)))))))


C/USER[10]>  (0-to-10 #'(lambda (x)
                         (if (= 4 x)
                             (error 'I-just-dont-like-4)
                             (/ x)))
                     '(division-by-zero I-just-dont-like-4)) 
NIL 1 1/2 1/3 NIL 1/5 1/6 1/7 1/8 1/9 
NIL
C/USER[11]> 

An alternative would be to generate some handler-case form at run time
and use EVAL or (funcall (compile nil `(lambda () ...))), but it would
probably be slower than just MEMBER.

-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

WARNING: This product warps space and time in its vicinity.