4 ms·
Debugging Lisp Part 4: Restarts
- OopsCriticality 11y agoThis has been a great series: clearly written, solid examples, and obvious utility. Thanks for doing it!
- agentultra 11y agoThis 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 11y agoIt'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 11y agoPL/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.
- agentultra 11y ago> It's not really at the core of the Lisp essence This isn't the first time this has been pointed out to me. Someone had pointed me to Kent Pitmans'[0] paper on Common Lisp's condition handling system. It is quite a good read. As I wasn't even born when Multics and PL/1 were released and was never old enough when they were around to have used them I do lack that context. To me, having started using CL around 2008, it is an essential "feature" of the Lisp experience. One that no other language I'm aware of implements and few, if any, operating systems implement today. Unix signals perhaps could emulate it? So... my apologies if my ignorance characterizes Lisp incorrectly but I still wish this style of error handling was more common. It is just so damn useful. [0] http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html http://www.nhplace.com/kent/Papers/Condition-Handling-2001.h...
- orthecreedence 11y agoI 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.
- phs2501 11y agoRestarts 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 11y agoYou 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 11y agoFair 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.