Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Condition Handling in the Lisp Language Family (2001) (nhplace.com)
52 points by Jtsummers on Nov 22, 2021 | hide | past | favorite | 14 comments


> Common Lisp provides parallel but unrelated operators such as HANDLER-BIND and HANDLER-CASE for dealing with handlers, and RESTART-BIND and RESTART-CASE for dealing with restarts. It was thought that these were orthogonal operations, requiring unrelated dataflow, that really didn't belong intermingled. The Dylan community has sought to coalesce these by making restarts into a kind of condition, and eliminating special binding forms for them.

I'm surprised to read about this bit about Dylan for the first time; I missed that in past readings.

I also coalesced handling and restarting in TXR Lisp. There are no restarts, only exceptions. An exception can be handled (which is the terminology for intercepted without unwinding the stack) or caught (which is the terminology for intercepting and unwinding). The symbol restart is provided as the root of the hierarchy for restarts; the reference manual encourages the practice of deriving restart-like exceptions from this symbol.

https://www.nongnu.org/txr/txr-manpage.html#N-00F77525


> The establishment of protocols is a sort of before-the-fact hedge against the "prisoner's dilemma"; that is, it creates an obvious way for two people who are not directly communicating to structure independently developed code so that it works in a manner that remains coherent when such code is later combined.

Sounds very cool, and the article seems to edify wrt functionality and abstract benefit, but how does it really help? See: the more-or-less complete abandonment of resumable conditions in other languages.

The practical differences between conditions and c++-style exceptions seem to be:

1. The stack is followed back, looking for a handler, rather than being destructively unwound- 'restarts' don't necessarily 'try again', they can continue from where the condition was thrown.

2. While unhandled (ERROR) 'fatal conditions' land in the debugger, nonfatal (SIGNAL)s imply that a sensible default has already been provided, and, relying on point 1, continue otherwise unfettered.

I remain curious about the kinds of practical architectural impact this functionality provides- how often does everyday lisp code signal nonfatal conditions? The hyperspec mentions that *break-on-signals* can be used for visibility there...


It's actually quite a good design idea because it is often the case that lower level code knows everything that can go wrong but has no idea what to do about it. Higher level code has this knowledge. For an example, I remember reading a C library for handling some common encoding. It simply swallowed format errors because it didn't want to fail the whole function for them and didn't have a way to communicate anything was wrong. In a conditional system this could have been a condition and the default function could be set up to ignore it, for example.


I think an approximation to this in C++ would be to have some thread-local objects called condition handlers that you set using some RAII construct (so, something that will replace the current condition handler and then later restore it when you leave the current scope), and then when some function experiences an exceptional circumstance, it calls some methods on the appropriate condition handler object. The condition handler is free to throw an exception itself if it wants to destructively unwind the stack, but it can also do other things.

It's through these condition handler objects that the caller can bidirectionally communicate with callees before they return.

In other words, it's like the visitor pattern, but rather than passing the visitor directly you do so through a thread-local variable, and also these visitors are designed to be composable.

A more modern version of this, straight from the ivory tower, are effects handlers, which I expect we'll see in more languages as time goes on. The cool thing about effects handlers is that they make it easier to reason about all these nonlocal effects due to some well-thought-out restrictions. They also have some good compositionality properties, and they appear to be able to be compiled down to code that doesn't need special runtime support (beyond what functional programming languages already provide). The example I'm somewhat familiar with is Koka: https://koka-lang.github.io/koka/doc/book.html#sec-handlers


> I remain curious about the kinds of practical architectural impact this functionality provides- how often does everyday lisp code signal nonfatal conditions?

From my time programming on Lisp machines (long time ago), I fondly remember wrapper functions (i.e. some macro wrapped around your own code) like "with-open-file" which would open a file and set up proper condition handling to close it on exit, regardless of what happened inside... you could always be sure that the file was closed when control left the wrapper, either normally or abnormally. And if you where creating a file and some exception occurred, the file would be removed and the file system left unaltered (as much as possible).


The way to do this in C++ is via RAII.

Thus instead of (with-open-file (mystream …) …) You do { mystream { class_whose_destructor_closes_the_file (…) }; … }

Which is uglier but in the end largely equivalent, as C++ implements unwind-protect in every execution scope.


IME, exception-handling works well as an error-handling mechanism where a very simple policy is being applied for a very wide range of error cases. Example: 0.0001% of the total data in this analysis pipeline is malformed for reasons a-z and A-K (and counting). Don't care; throw out that one datum and continue.

Some web request went badly. We're concerned about leaking internal details to the caller. So, you reply with a generic error code, log something about it for operator investigation, and make sure that any other state changes were rolled back along the way.

Condition systems invert that relationship. Its not just a failure to open a file, but a failure to open one specific file for a specific use case in the pipline out of several that are needed in total. In a condition system, the caller needs to set up a handler for each one of those use cases, and possibly bind some additional discriminating information to the arguments to disambiguate them on the way back up the stack.

If a reasonable fallback policy is available, I'd much rather partition the pipeline up such that the fallbacks are applied on a case-by-case basis in the control flow of the caller. Idiomatic Python code does this all the time with exceptions. Idiomatic code with Optional types does so, too. Both methods are well-suited to the task, and I have never found myself wishing for a condition system when using them.


Talking about that, I found ~old (relatively) slides from phoe (author of a CL condition system book) about control flow:

https://phoe.github.io/slides/control-flow.pdf

enjoy


There is the book "The Common Lisp Condition System" by the same person : https://link.springer.com/book/10.1007/978-1-4842-6134-7


Just going through this book. I am moving slowly through it, since I am no Lisp guru, but it is written very clearly and seems very thorough so far.


You can implement restartable conditions in a language like C++ through judicious placement of your catch clauses.

The problem is the management of such conditions is quite local. The power of the common-lisp approach is that the caller can set policies this way which makes the abstractions easier to write.

Not everybody thinks seeing things this way is a good idea. After all Lisp is a systems programming language that consists of a large number of ropes any one of which you can use to hang yourself. IMHO it remains the most powerful, programmer-time efficient and, frankly fun language I’ve ever had the pleasure of being paid to use.

C++, which I mostly use these days, similarly has a bunch of ropes-to-hang-yourself. Fewer, of course, as it isn’t as powerful, but even it seems to intimidate and enrage people.


(2001)


Aha, so a relatively recent article!


Thanks, added. I’m usually good about adding the date.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: