4 ms·
> 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
by smegsicle 5y ago
> 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...
- jbjohns 5y agoIt'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.
- jcynix 5y ago> 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).
- gumby 5y agoThe 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.
- kmill 5y agoI 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 https://koka-lang.github.io/koka/doc/book.html#sec-handlers
- brandmeyer 5y agoIME, 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.