def with_handler(ctx, h):
return {'handler': h, 'next': ctx}
def with_restart(ctx, name, r):
return {'restart': (name, r), 'next': ctx}
def find_handler(ctx, err):
if 'handler' in ctx:
return ctx['handler']
elif 'next' in ctx:
return find_handler(ctx['next'], err)
else:
raise Exception('Unhandled error %s' % err)
def find_restart(ctx, name):
if 'restart' in ctx and ctx['restart'][0] == name:
return ctx['restart'][1]
elif 'next' in ctx:
return find_restart(ctx['next'], name)
else:
return error(ctx, 'No restart named %s' % name)
def error(ctx, err):
find_handler(ctx, err)(ctx, err)
raise Exception('No handler found for %s in %s', err, ctx)
def do_stuff(ctx):
results = []
for i in range(100):
results.append(do_little_stuff(ctx, i))
return results
class alpha(Exception):
def __init__(self, text):
self.text = text
def do_little_stuff(ctx, i):
def restart(ctx):
raise alpha('text: %d' % i)
ctx = with_restart(ctx, 'use-alpha', restart)
try:
if i % 3 == 0:
error(ctx, '%d %% 3 == 0' % i)
return i
except alpha as e:
return e.text
def handler(ctx, err):
find_restart(ctx, 'use-alpha')(ctx)
ctx = with_handler({}, handler)
print do_stuff(ctx)
Note how the top level provides a contextual handler which chooses to use the alpha restart, while the restart itself is provided by the mid-level do_stuff, while the low-level do_little_stuff both provides the restart & signals the condition.Add in some type detection &c. and this could start to be useful. But it'd still be hellaciously verbose compared to the Lisp:
(defun do-stuff ()
(loop for i below 100 collect (do-little-stuff i)))
(define-condition mod-three () ())
(defun do-little-stuff (i)
(restart-case
(progn
(when (zerop (mod i 3)) (error 'mod-three))
i)
(use-alpha () (format nil "text: ~d" i))))
(handler-bind ((mod-three #'(lambda (c) (invoke-restart 'use-alpha))))
(do-stuff))Lisp has been doing things newer languages still don't do since before a lot of modern programmers were even born. And there are relatively few warts for that age (upcasing and the pathname functions are the warts which leap to mind)!
One way to implement that is that error-event-raising is just a function which examines the dynamic context for error handlers, and calls them until one transfers control (e.g. by throwing an actual exception). That's pretty simple to implement in any language which has exceptions, panics or similar control-transfer structures.
It gets more complex when you have restarts. The nice thing about Lisp is that all the complexity can be hidden with macros; with other languages you have to be explicit every time.
> If the user is not handles the error(forgets about a type or a new type is added in an update) will the user notice the error happened?
In Lisp, ERROR invokes the debugger if the condition is not handled. So an unhandled error condition pauses execution. One cool thing is that the user can use the debugger to invoke restarts. It's extremely nice.
There's also CERROR, which establishes a restart to return from itself — so some errors can be continued on from — WARN, which offers automatically, mufflable warning output, and SIGNAL, which offers non-erroneous condition signalling (i.e., while ERROR never returns, SIGNAL can).
It's really, really full-featured but also easy to use.