
Debugging Lisp Part 4: Restarts - malisper
http://malisper.me/2015/08/05/debugging-lisp-part-4-restarts/
======
orthecreedence
I built cl-async and a lot of its satellite libraries completely ignoring
restarts and the CL error handling paradigm in general, opting more for a
"well, an error occurred, catch it and send it to a callback" method.

A few lengthy discussions with some lispers who were more well-versed in CL's
error handling than what would be the equivalent of try/catch/throw completely
changed my view on lisp's abilities. The reasoning behind using handler-bind
instead of handler-case just blew my mind once I understood it. It was a hard
concept to grasp, but I'm so glad those guys pounded it in.

I reprogrammed the error handling in cl-async/blackbird/wookie/etc to use
handler-bind/restarts and now everything is so much clearer. You get full
stacktraces when in debug mode, but also have the option to pass errors around
as objects if in production.

CL's error handling definitely was a "wow" moment for me once I got it.

------
agentultra
This is one of my favorite features of Common Lisp programming. The nice thing
about interactive restarts is that the whole stack is there... so all of the
previous functionality is available as discussed previously in the series. You
can interactively inspect an object to see what slot values are causing a
conflict and not only change the value and continue but recompile the method
that caused it as well. And of course clever code could do this as well.

Restarts are a very nice way of handling known error conditions and I wish
more languages had stolen this feature from Lisp before macros or garbage
collectors and things.

~~~
kazinator
It's a good feature for building big programs that have lots of layering. It's
not really at the core of the Lisp essence; Lisp dialects other than Common
Lisp and some of its ancestors do not have this feature.

> _I wish more languages had stolen this feature from Lisp_

The terminology and some of the semantics of CL's condition may have been
derived derived from concepts in IBM's PL/I programming language!

Similarities:

* PL/I's xceptions are called "conditions" in PL/I.

* Conditions are "signaled" (by a construct called SIGNAL).

* The "on unit" construct (ON statement) resembles condition handling and restarts (functionality of handler-bind and restart-bind rolled into one). On units are dynamically scoped, and when a condition is raised they are searched without discarding the stack ("the whole stack is there", in your words). On units can return to the source of the signal, or execute a GO to the surrounding procedure, which then triggers unwinding: exactly like a (go ..) in Lisp jumping out of a restart lambda.

* Conditions in PL/I are used not only for errors but for giving advice to low level code on what to do. For instance the printing system signals an "endpage" condition, and the higher level code can provide an on unit to do what it wants at the end of a page. This on unit returns to the lower level code.

I've never written code in PL/I, but I studied the condition system.

~~~
hga
PL/1 had such an influence. Multics had its own version of it as its system
language, which had "on-conditions", which per
[http://www.multicians.org/pl1-raf.html](http://www.multicians.org/pl1-raf.html)
made optimization difficult. Bernie Greenberg was a major Multics "kernel"
hacker, and wrote Multics Emacs in MACLISP and PL/1 before joining Symbolics.
Per vague memory and Wikipedia
([https://en.wikipedia.org/wiki/Bernard_Greenberg](https://en.wikipedia.org/wiki/Bernard_Greenberg)):

 _Greenberg was involved in the design of the "New Error System" at Symbolics,
which in turn influenced the condition system adopted by ANSI Common Lisp._

And per the referenced citation
([http://www.nhplace.com/kent/CL/Revision-18.txt](http://www.nhplace.com/kent/CL/Revision-18.txt)):

 _Many aspects of the NES were inspired by the (PL /1) condition system used
by the Honeywell Multics operating system._

------
phs2501
Restarts are nice.

The thing is, though, is that restarts are relatively easy to add to any
language that has well-supported structures for non-local control flow;
they're just a protocol, after all. That's really what Common Lisp has which
most other languages lack. Common Lisp (very intelligently, IMHO) did not
conflate error handling with non-local control flow; they are different
concepts and deserve to be treated independently.

Specifically, the lexcal scoping of BLOCK and TAGBODY labels and the fact that
RETURN-FROM and GO will unwind the stack if necessary to get to the target
label make creating something like HANDLER-BIND/RESTART-BIND relatively easy.
(There is also of course THROW and CATCH [which don't really have anything to
do with error handling in CL, they're just structures for dynamically-scoped
stack unwinding], but I've generally found the need to create explicit labels
for those less elegant than using lexical scoping. You can always use a
closure if you need to store the lexicaly-bound state to be used somewhere
else, and the reverse is not as easy...)

You obviously also need UNWIND-PROTECT to keep things safe when the stack
unwinds, but most languages successfully manage to get that with something
like try-finally (or RAII in C++'s case, even if I find it clumsy sometimes).

~~~
jsnell
You can implement the CL condition system in pretty much any language, but
it's not going to be useful. The value of the condition system is strongly
tied to it being used consistently everywhere in the language and in the
libraries.

~~~
phs2501
Fair point.

What I was attempting to convey is that the reason I think the CL condition
system works so well is that at its core it doesn't attempt to manage control
flow directly; i.e. neither HANDLER-BIND nor RESTART-BIND unwind the stack on
their own, and even ERROR only gets its behavior by falling back to INVOKE-
DEBUGGER if unhandled. Because CL has good tools for doing non-local control
flow that aren't bound up in its condition/exception system it winds up
strictly more expressive (IMHO) than languages that conflate these.

What I've noticed from languages that have traditional try/catch exception
handling (especially when that's the only non-local control flow available) is
that the mechanism of error handling is seen as DEEP MAGIC by their users. If
you separate it out into non-local control flow and a protocol for
communicating exception handlers as CL does, the "magical" properties go away
since it can be seen how the condition system could be implemented.

Or at least that's how it worked for me.

------
OopsCriticality
This has been a great series: clearly written, solid examples, and obvious
utility. Thanks for doing it!

