
Restarts in Common Lisp - sulami
https://sulami.github.io/posts/common-lisp-restarts/
======
phoe-krk
Nitpick:

> While Common Lisp calls them “conditions”, I will stick with “error” in this
> post, because I don’t think “condition” is very self-explanatory if you’re
> not familiar with the language already. Please forgive my inaccuracy.

This is misleading. It is possible to signal conditions that are not errors,
and therefore do not require stack unwinding, like errors do. The condition
facility allows one to execute arbitrary code with arbitrary arguments at the
SIGNAL site.

`SIGNAL` walks the list of condition handlers and executes the type-matching
ones in order of their binding. If a handler returns normally, then next ones
are executed; if all matching handlers are exhausted, `SIGNAL` returns, and
code execution continues.

    
    
        CL-USER> (handler-bind ((condition (lambda (c) (format t ";; Foo: ~A~%" c)))
                                (condition (lambda (c) (format t ";; Bar: ~A~%" c)))
                                (condition (lambda (c) (format t ";; Baz: ~A~%" c))))
                   (signal 'condition)
                   42)
        ;; Foo: Condition CONDITION was signalled.
        ;; Bar: Condition CONDITION was signalled.
        ;; Baz: Condition CONDITION was signalled.
        42
    

Of course, a handler can perform a non-local transfer of control elsewhere,
e.g. by using `GO` or `THROW`. In such a case, control is transferred out of
`SIGNAL`, so next handlers are - naturally - not invoked.

While I understand that this post is trying to appeal to language newcomers,
simplifying the whole condition system to an "error try-catch" is a drastic
misrepresentation of the condition system that will leave the newbies with the
impression that that's all that is possible with that feature. Since it seems
that the author is a Lisp newcomer as well (which I wholeheartedly admire! :),
I'd advise him to use the original notation of "condition", instead for the
one of "error".

~~~
michaelfeathers
Correct me if I'm wrong but can't all of the cases covered by conditions be
handled in an OO language by providing an object that one can register
conditions with and call signals upon? I'm trying to imagine library support
for languages that don't support conditions directly.

~~~
phoe-krk
You can implement a condition system in any programming language that has
dynamically scoped variables. (That was how the original CL condition system
was implemented on Lisp that did not have a condition system - only dynamic
variables.)

You can implement dynamically scoped variables with a stack of environments,
onto which you push entries atop when entering new dynamic scope and from
which you pop entries when leaving a dynamic scope. (That is how the original
dynamically scoped systems, including Emacs Lisp, were implemented.)

Example: [https://github.com/svetlyak40wt/python-cl-
conditions](https://github.com/svetlyak40wt/python-cl-conditions)

------
aidenn0
Aside from restarts, the core difference between the Common Lisp condition
system and exceptions in most other languages is that the exception handler
can run before the stack is unwound.

Having the call-stack available at the time the handler runs can be very
useful; many modern dynamic languages will save some of that information in
the exception object. It's still surprising to me that so very few languages
have copied this feature (or even independently discovered it).

~~~
beagle3
> the exception handler can run before the stack is unwound.

Old basic had an error handling model that is exceptionally elegant in some
cases: "on error goto ...", with an an indication (errline, I think?) of where
the exception happened, and then you could "resume next" to continue (perhaps
after fixing the error condition, if the error handler is somewhere else) or
directly restart at an earlier point if that makes sense.

It's simple, a little goto-ish, and has abuse potential - but if actually
used, tended to end with robust code where the "usual path" was the simplified
"that's what we usually do", and all the "in case something was unexpected"
are listed independently. C code using goto for error handling (e.g. in the
linux kernel) has similar properties.

try/catch, in all languages it exists, does not let you restart in the middle
unless all the error handling code is in-line (which is comparatively
disgusting IMO); lisp conditions do, and more - but they only exist in Lisp.

~~~
dfox
> only exist in Lisp.

Windows's SEH is essentially simplified CL condition system.

~~~
simiones
Not really, though it does share the property that it executes user code
before unwinding the stack (the exception filters), and that the user code can
choose to mark the exception as handled and allow the program to continue
without unwinding the stack.

However, there is no Restart system, allowing the code that raises an
exception to also declare how it can be handled. And there are no alternative
exception handling strategies than searching the stack and unwinding in case a
suitable handler is not found. Unwinding is not a property of Conditions in
general CL, it is just one of the strategies that can be used when signalling
a condition.

------
chaitanya
Many years ago I wrote about restarts in CL using the example of a CSV parser:
[https://lisper.in/restarts](https://lisper.in/restarts)

Fun fact: the restarts based CSV parser was based on some real world work me
and a colleague had done for an online travel portal. I wrote about it here:
[https://www.reddit.com/r/lisp/comments/7k85sf/a_tutorial_on_...](https://www.reddit.com/r/lisp/comments/7k85sf/a_tutorial_on_conditions_and_restarts/drceozm/)

------
metroholografix
I find that Practical Common Lisp has not aged well. Maybe it's the
"practical" part with the specific examples the author used -that are now
terribly dated- that is mainly responsible, but the end result is that I no
longer recommend it.

My goto recommendation for newcomers is Norvig's PAIP which is full of
insightful moments and unbelievably good code and for people with more
experience "Let over Lambda" by Doug Hoyte, which is a mind-blowing book
mainly because of the time-transcendent nature of the examples Doug chose.

~~~
sulami
I'm planning to read PAIP over the upcoming weekend as a follow-up, I've
already got it sitting here.

I know next to nothing about AI, beyond the basics of RNNs, so I'm hoping to
learn a lot.

~~~
weavie
You may need more than a weekend!

~~~
billsix
[https://norvig.com/21-days.html](https://norvig.com/21-days.html)

------
kazinator
In TXR Lisp, I unified condidtions and restarts into one thing: exceptions. By
programmer convention only, restarts are exceptions derived from _restart_.

The documentation has dialect notes about this with a comparative example:

[https://www.nongnu.org/txr/txr-
manpage.html#N-00F77525](https://www.nongnu.org/txr/txr-
manpage.html#N-00F77525)

------
artemonster
Can somebody please also elaborate what is the difference between condition
system in CL and "new thing" algebraic effect handlers? On the surface I see
no difference, other that the latter is being a more formalized version of
conditions? But we have same properties: 1. signal a condition to some
handler(s), possibly with value. 2. reify the stack into a (delimited up to
level with handler) continuation and let handler decide what to do with it:
save, restart, resume with some value. Am I missing something?

~~~
dfox
The CL condition system is completely orthogonal to any concept of
continuations or stack unwinding. Both condition handlers and restarts are
plain functions and any kind of stack unwinding has to be implemented inside
these functions (by means of TAGBODY/GO, BLOCK/RETURN-FROM or THROW/CATCH).

~~~
hencq
To add to that, as an example, here's how you could implement something
similar in Clojure [1]. It just relies on dynamic scope to run the handler
without unwinding the stack.

[1]
[https://gist.github.com/msgodf/6f4e43c112b8e89eee3d](https://gist.github.com/msgodf/6f4e43c112b8e89eee3d)

------
jgrant27
[https://imagine27.com/error-handling-or-the-emporors-old-
clo...](https://imagine27.com/error-handling-or-the-emporors-old-clothes)

------
mrow84
A long time ago I wrote a conditions system for python:
[https://code.google.com/archive/p/pyconditions/](https://code.google.com/archive/p/pyconditions/)

I wouldn't recommend using it, but it is an interesting error/control model,
and I always wondered why it hasn't seen wider implementation - I presume
because it is closer to the "dangerous" end of the power spectrum.

------
olah_1
Hey Sulami, this is off topic, but how did you get those nice sidebar
footnotes? What theme are you using? Or how did you edit the css?

~~~
sulami
This is the tufte-css, with some modifications.

Raw css is here: [https://github.com/edwardtufte/tufte-
css](https://github.com/edwardtufte/tufte-css)

My additions on top (and all other source) here:
[https://github.com/sulami/sulami.github.io/tree/develop/css](https://github.com/sulami/sulami.github.io/tree/develop/css)

------
nabla9
The condition system in Common Lisp is really amazing piece of work. Error and
exception handling is just subclass of larger set of possible uses called
conditions.

------
shadowgovt
Interesting. This looks like an implementation of the abstract computer
science notion of continuations
([https://en.wikipedia.org/wiki/Continuation);](https://en.wikipedia.org/wiki/Continuation\);)
neat to see them used this way.

~~~
phoe-krk
The Common Lisp condition system is actually just a dynamically-scoped hook
system.

Handlers are the actual hooks; conditions are the object that may trigger some
of the hooks when they are signaled. Restarts are an addition on top of the
hooks that allows for easier non-local transfer of control; they provide
little that handlers do not provide unless you count the Lisp debugger - by
design, the _only_ way to leave the debugger is by using a restart.

In other words: no continuations here.

~~~
shadowgovt
Interesting. I'm not sure I understand; what do the dynamically-scoped hooks
lack that continuations would give you?

~~~
sedachv
In Common Lisp, you can only transfer control up the stack. With
continuations, you get multiple stacks that you can transfer control between.

------
nickpsecurity
With a quick DuckDuckGo-ing, I found this intro to be really understandable
compared to most links I read:

[https://z0ltan.wordpress.com/2016/08/06/conditions-and-
resta...](https://z0ltan.wordpress.com/2016/08/06/conditions-and-restarts-in-
common-lisp/)

------
nerdponx
R, continuing its theme of being a weird Lisp with C syntax and really bad
function names, has a condition system:
[https://adv-r.hadley.nz/conditions.html](https://adv-r.hadley.nz/conditions.html)

------
slifin
Keep meaning to look at this, Clojure has some libraries for this I could use:

[https://github.com/clojureman/special#alternative-
libraries](https://github.com/clojureman/special#alternative-libraries)

------
kazinator
The author doesn't seem to understand that not all exceptional situations are
errors, which suggests he is a complete newbie to exception handling, which
explains why he didn't propose the word "exception" instead of "condition".

~~~
metroholografix
Not all conditions signify exceptional situations. The author conflated error
with condition but you're doing the opposite of conflating condition with
exception.

The big plus of the Common Lisp condition system is that it gives you a
programmable substrate that you can use to implement a wide variety of
signaling and control flow systems. It does give you error and exception
handling -on top of the condition system- but that shouldn't shoehorn your
thinking into just these problem domains.

~~~
Jtsummers
As an example of non-exceptional condition signaling:

[http://jacek.zlydach.pl/blog/2019-07-24-algebraic-effects-
yo...](http://jacek.zlydach.pl/blog/2019-07-24-algebraic-effects-you-can-
touch-this.html)

Progress can be signaled, and then dealt with if needed. In headless mode it
won't be handled. In a console it could be a progress bar or statement. In a
GUI, update a variable that changes the status display.

And it ends up as little more than a no-op if there are no handlers for it.
Nothing exceptional about it, but creates a great deal of flexibility in
managing a system.

~~~
kazinator
There is also nothing conditional about reporting progress, except in the
sense that the progress is an indication of state.

According to a Honeywell PL/I reference manual, "a condition is a state of the
executing program".

The progress of some activity being 50% through is a state of the executing
program.

(All the built-in PL/I conditions are exceptional situations though. This is
really the same use of the word "condition" that we find in "condition status
register", a machine word that tells us via flags that exceptional situations
have happened like numeric overflow.)

