[HN Gopher] Show HN: My book, The Common Lisp Condition System ___________________________________________________________________ Show HN: My book, The Common Lisp Condition System Author : phoe-krk Score : 100 points Date : 2020-10-23 09:31 UTC (13 hours ago) (HTM) web link (www.amazon.com) (TXT) w3m dump (www.amazon.com) | db48x wrote: | I think section 4.11 is the most important of the new material. | phoe-krk wrote: | Thank you. I am taking a big risk by making the statement in | 4.11 (in the online Appendix E), since it directly implies that | _none of C++, Java, Python et al actually do any exception | handling at all_. It 's all control flow, always has been. | | Still, I've actually wanted to write it there since I | considered it to be important, and because that argument came | into existence during the first Hacker News discussion about | the book (and therefore is a product of more than just my | mind). | db48x wrote: | You are kind of implying that :) | | It may come down to how we actually use exceptions in those | languages, however, that makes the difference. I worked on a | system written in PHP + Bash which had been built to have | some restartability. It was a horrible kludge, and to improve | the system you had to pore over the logs to try to divine | what had happened, but there was an attempt. | phoe-krk wrote: | Yes, I'm implying that. For instance, a lot of Python's use | of "throw" is not really because we want to signal an | exception in some Python code, we just want to unwind the | stack while passing some values and we can't do it in any | other way in the language. | | Again, that's just control flow, and that's because | throwing exceptions is the only way of performing non-local | exits in those languages. Actual exception handling starts | when the existing control flow cannot handle some situation | that has occurred, and someone - or something - needs to be | able to create new control flow dynamically in order to | continue program execution and therefore the process that | was ongoing there. | | Or, you know, we can do the industry standard: crash the | program, and restart the thread or daemon or virtual | machine or server cluster in question in hope that turning | it off and on again helps. /s | javert wrote: | If anyone else is wondering what the Lisp "condition system" | is... | | Apparently it's analogous to "exception handling" in other | languages, but with some extra features. | | Props to the author for getting out his first book! | minerjoe wrote: | It's not that "analogous". | | Take a peek, it doesn't unwind the stack unless you ask it to, | which gives it much more power than exception handling in | languages such as Java. | outworlder wrote: | That's the problem with Lisp features - you don't get to | appreciate how powerful they are until you are way deep in | the woods, at which point you are now another 'convert' | trying to convince others to follow. | | I like Scheme but having seen what the condition system could | do on Lisp Machines decades ago makes me feel like we are | currently living in a computing dark ages. | TurboHaskal wrote: | This is exactly the issue. Growing appreciation for this | kind of stuff often requires a level of understanding that | a lot of the people in the industry lack. You can't miss | what you don't know about. | | I'll give some examples: | | You cannot appreciate the extreme simplicity of Pascal or | other Wirthian languages if you never wrote your own | compiler. For the average developer, it is just a | procedural language with weird excessive syntax, annoying | separation between interface and implementation and way too | picky about where you declare your variables. | | You cannot appreciate Forth if you don't know how the | machine works and see the beauty of threaded code. Forth | then just becomes this reverse Polish notation thing that | some crazy people love. They really must be nuts, as I'm | solving my problems with C and JavaScript just fine. Why do | they like RPN so much and why can't they just shut up about | it? | | So the same goes for Lisp and Smalltalk. These are not | regular languages, they have operating system aspirations. | What a small group of people were doing with Lisp Machines | when everyone else were using line editors is completely | unknown for the average developer, they just see the | parenthesis and run away. | | As you mention it is also the reason why I'm partial to | Common Lisp over something like Scheme and Clojure. I mean, | I like those a lot as well, but they don't offer me much | over plain JavaScript and I'd rather use APL :) | Jtsummers wrote: | > You cannot appreciate the extreme simplicity of Pascal | or other Wirthian languages if you never wrote your own | compiler. For the average developer, it is just a | procedural language with weird excessive syntax, annoying | separation between interface and implementation and way | too picky about where you declare your variables. | | You don't need to have written a compiler. Just have | wanted real modularity. You can get it with some popular | and mainstream OO languages (Java, C#), but it's horribly | broken in many other mainstream languages (C++). | | Separating interface from implementation _shines_ when | dealing with larger teams, and can really be nice for | compile times which improves feedback loops. | oalae5niMiel7qu wrote: | You haven't seen a fast feedback loop until you've | programmed in Lisp. And it has none of that "interface vs | implementation" nonsense. | Jtsummers wrote: | I agree that Lisp offers a fast feedback loop, and I have | programmed in it (though not professionally) for about 14 | years now. | | Regarding '"interface vs implementation" nonsense', | that's kind of nonsense to call it nonsense. Most of us | don't (professionally) get to work in Lisp or other non- | batch compiled languages. In those cases, the separation | (in sane language implementations) can lead to _much_ | faster compilation time. But they also help to work with | teams at scale, and make it easier to substitute | implementations without having to recompile the entire | thing (though you 'll still usually have to relink it). | phoe-krk wrote: | I sincerely hope that my book aids this problem that you | are speaking of - both by allowing programmers and | implementers of other languages use the techniques that | originate in Lisp, and to try and bash at the Lisp-as-a- | meme prejudice that I've seen in many places by showing | what the condition system is actually useful for. | phoe-krk wrote: | Not exactly. The Common Lisp condition system is usable for | handling error situations, but it's usable for much more as | well; the act of signaling a condition does not immediately | unwind the stack, like throwing an exception does, and the | restart system allows for actively choosing which pieces of | dynamically provided code should be run instead of just blindly | executing them in order. | | This allows for some extra flexibility with regard to how to | handle errors, and with regard to how conditions may be used | without any unwinding whatsoever. | | (Thanks for the props!) | ahefner wrote: | It's hard to appreciate the value of restarts if you haven't | seen it fully integrated into the environment like on an old | lisp machine. Given any exceptional condition deep down in a | program, assuming appropriate restarts are bound, it can pop | the debugger and let you choose how to proceed, but handlers | can also be bound to handle conditions / invoke restarts | programmatically. Restarts may be bound at different layers of | granularity so if you had a command operating on a set of | files, one one of them fails to open, the avaiable restarts | might be "Retry command", "Skip this file", "Retry open", | "Provide alternate filename", etc. It's sort of a missing link | between interactive and batch/scriptable environments that | current operating systems are missing. | | (My Common Lisp is getting rusty, sorry if I've slightly | jubmled the condition/handler/restart lingo) | hoytech wrote: | Congrats! The condition system isn't very well described | elsewhere, especially relative to how cool it is. I just ordered | your book, looking forward to reading it. | phoe-krk wrote: | Thank you for the congrats, and the fact that it wasn't | described anywhere is the reason for my original frustration, | which in turn is the reason why this book exists. | | I hope that the book is useful for you; in case of any | questions, please feel free to ask me and/or make issues on the | GitHub repository of the book. In the worst case, I'll create | or expand the book's errata... or write yet another appendix. | nickdrozd wrote: | To what extent is the CL condition system inherently tied to | Lisp(s)? Is there anything about it that makes it a natural fit | for Lisp but not for other languages? | | Macros, for example, are a natural fit for Lisp because of the | parentheses. It would be difficult to add Lisp-style macros to a | language like Python because Python doesn't have Lisp | parentheses. In contrast, there's nothing about multiple | namespaces that is particularly tied to Lisp. Common Lisp and | Emacs Lisp have multiple namespaces, but Scheme doesn't. Python | doesn't have them, but it just as easily could. | | So is the condition system more like macros or more like multiple | namespaces? | nickdrozd wrote: | From Appendix E: | | > It is also noteworthy that this aspect of the condition | system is fully independent from Lisp's homoiconicity; rather, | it is a consequence of the way other programming languages are | designed. For instance, when one divides by zero in a Java | program, then there is nothing carved in stone which would | prevent the language from winding the stack further and | executing some code that will analyze the dynamic environment | in which the error happened, calling all error handlers found | in the dynamic environment, and then--if no handler transferred | control outside the error site--either entering an interactive | debugger of some sort or giving up and crashing. However, Java | goes a different way: it immediately destroys the stack by | throwing an exception. That is a design choice which was made | by the Java creators; Lisp's homoiconicity has nothing to do | with this fact, as can be demonstrated by the multiple | independent implementations of condition systems in non- | homoiconic languages that we have mentioned earlier. | phoe-krk wrote: | Oh golly. To think that people would quote my own words to | answer Hacker News questions... | | (Thanks for the assist!) | dasyatidprime wrote: | What phoe-krk mentions is indeed the main thing that blocks | "real" integration into existing languages with exceptions: | they always unwind before executing the handler. (JWZ even | complained about this while writing about Java.) | | To expand on their comment: if `throw` in Java or C++ is | similar to `error` in CL (or `throw` in some special cases), a | `catch` clause in Java or C++ is equivalent to a label, where | the `try` binds a handler that exits to it immediately. There's | no equivalent of putting code in the handler other than a | single unwind-and-jump, and there's no equivalent of restarts. | | In CL, a condition handler gets called _on top of_ the existing | stack and can inspect what 's going on before choosing where to | exit to. Other functions in the call stack can provide | alternative exits (restarts), like "continue processing anyway" | or "substitute a placeholder value"; these are dynamically | named, rather than lexically bound like `catch` . So there's a | lot more possible decoupling, at least in theory. The | equivalent of `finally`/destructors is `unwind-protect`, which | has to interoperate with the condition mechanism but doesn't | deal with conditions itself. | | In C++ or Java, you could implement the restarts with a | (thread-local) stack of restart descriptions plus try/finally | or constructor/destructor, and the same for handlers, and then | do your nonlocal exits with specialized throwables. I did | something similar in Lua, in fact, while trying to extend it | into a fancier language. But a "normal" `throw` will bypass all | of that. That's not _dangerous_ if you do the unwind-protects | properly, but none of your existing libraries will be built for | it, and the results will be kind of anemic. | | In the Java-style objects+throw/catch world, similar things can | be achieved by toggling "what to do if X happens" state or | plumbing callback pointers through the object graph beforehand, | which is similar but more ad-hoc, and possibly harder to add to | existing systems. That said, the CL style proper is very tied | to the call stack, which can also make things tricky. | oalae5niMiel7qu wrote: | In C++, you can override the __cxa_throw() function to | implement a fully-fledged condition system that calls | handlers on top of the stack instead of unwinding first. Call | the real __cxa_throw if there's no dynamic handler. | | To top it off, you can provide a "restart" class that this | new __cxa_throw treats like ordinary C++ exceptions, and | throw an instance of it to perform the "exit". | | I have no idea if this hack would comply with the standard, | but it works with GCC. | | You'd be missing COMPUTE-RESTARTS, however, so there'd be no | asking the user where to jump. | phoe-krk wrote: | Could you link me to any sources for that GCC behavior? | | Also, if we can have dynamically established handlers, then | we could also have dynamically established restarts (even | if by means of a dynamic variable implemented via a lexical | variable + a destructor), and therefore we could have a | COMPUTE-RESTARTS of our own and then be able to invoke it | arbitrarily as well as invoke individual restarts. | phoe-krk wrote: | Thanks for the assist! I have one comment: | | _> The equivalent of `finally` /destructors is `unwind- | protect`, which has to interoperate with the condition | mechanism but doesn't deal with conditions itself._ | | It actually doesn't need to interoperate with the condition | system; it has to interoperate with the stack-unwinding | primitives - `go`/`tagbody`, `return-from`/`block`, and | `throw`/`catch` - that are one of the foundations of the | condition system. | | If it works with those, then it works with a condition | system, since all control flow that happens inside the | condition system is a derivative of those primitive | operators. | phoe-krk wrote: | It is the latter. | | It is possible to implement a condition system on top of any | language that has dynamic variables, a `finally`-style | construct and some mechanism for unwinding the stack. Since | dynamic variables are implementable on top of lexical variables | and `finally`, it's basically just about unwinding the stack | and `finally`. | | The main issue is how a condition system would fit with any | existing system which likely works by immediately unwinding the | stack rather than allowing to wind it further; that's the case | e.g. in Java or C++. | jbjohns wrote: | Isn't it possible to do in any system with CPS as well? I've | not tried it but I would expect to be able to have a | "conditions system" using the CPS monad in Haskell, for | example. And Haskell doesn't even have variables the way most | programmers think of them. | phoe-krk wrote: | I haven't explored the CPS topic deeply, so I cannot really | answer. As far as I understand, CPS preserves the state of | the program or stack information by storing it in closures | that are ready to be called at any time, whereas a | condition system simply does not unwind the stack by simply | not unwinding it and executing on top of the already | existing stack, ready to both return control to the | signaling code and to transfer it somewhere up the stack. | | In its nature, a condition system is simply a means of | executing code that has been provided dynamically, | including transfers of control. I think that a closer term | would be algebraic effects, which seem to be an equivalent | of a condition system in a strictly typed strictly | functional world. | jbjohns wrote: | CPS can be kind of thought of like a normal programming | language with the modification that every function takes | an additional parameter "the rest of the program" and | calls this instead of "return". Of course once this | mechanism is in your language you might have multiple | "rest of the program" parameters which the function can | pick between. | | I found this old thread talking a bit about the lisp | condition system [1] which also mentions implementing it | in a typed manner. | | I used Lisp for a while myself as my favourite language | (the condition system was part of the reason) but the | problem I ended up switching to Haskell because it has | much of the power macros provide coupled with a very | strong type system. That's why the potential occurred to | me that CPS can probably implement something effectively | equivalent condition system. | | [1] http://lambda-the-ultimate.org/node/1544 | phoe-krk wrote: | I see. I know that it is possible to transform primitive | CL control flow operators into CPS, as it is shown on | https://gitlab.common-lisp.net/cl-cont/cl- | cont/-/blob/master..., so I assume that it is also | possible a condition system into CPS as a derivative of | those operators and then possibly optimize it further to | take CPS-specific code traits into account. | nradov wrote: | In principle most other compiled languages could have macros | that manipulate the abstract syntax tree. Lisp makes it easy | since there is little separation between the source code and | AST, but Scala now has macros despite using totally different | syntax. | flubert wrote: | Is this targeted more at users of the condition system, or | implementers who are writing their own common lisp compilers? | | From the Amazon blurb: | | >In part 1 of The Common Lisp Condition System, the author | introduces the condition system using a bottom-up approach, | constructing it piece by piece. He uses a storytelling approach | to convey the foundation of the condition system, dynamically | providing code to alter the behavior of an existing program. | Later, in part 2, you'll implement a full and complete ANSI- | conformant condition system while examining and testing each | piece of code that you write. | phoe-krk wrote: | Both. The book contains an explanation of how to derive the | condition system from the primitive operators of Common Lisp, | which should be useful both for users that try to gain some | intuition or broaden their understanding of how the condition | system works under the hood, and for people who want to | implement a condition system in a programming language without | one - be it Common Lisp or a different language. | | Implementing a condition system in a Common Lisp implementation | is actually a mostly solved problem, since one can use e.g. | https://github.com/phoe/portable-condition-system or borrow | some code from an already working Common Lisp implementation. | gibsonf1 wrote: | Just bought it - Thanks!!! | phoe-krk wrote: | Thanks, hope that it serves you well. | phoe-krk wrote: | Author here. | | _The Common Lisp Condition System_ is my first book and it was | previously discussed on Hacker News[0] as soon as the Apress page | for the book was first posted. | | The HN discussion was very fruitful and insightful and prompted | me to add more content about the condition system in general. Due | to time constraints and the flow of working on the book, it was | impossible to add this new stuff to the actual body of the book, | so me and Apress have decided to publish this content as an | appendix named _Discussing the Common Lisp Condition System_ and | release it[1] on the Internet. The appendix is free to download | and use in any way and I 'd like to once again thank everyone who | participated in the original thread. | | The book is currently available for purchase on Apress[2] (with | chapter samplers) and Amazon[3]. | | The full source code is available on GitHub[4] and should be | buildable under any conforming C compilers and any conforming | Common Lisp implementations. I'm available for answering any | questions about it and merging any PRs that might arrive. | | I hope that the book is helpful in general and wish everyone who | decides to try it a good read. | | AMA. | | [0] https://news.ycombinator.com/item?id=23843525 | | [1] https://github.com/Apress/common-lisp-condition- | system/blob/... | | [2] https://www.apress.com/us/book/9781484261330 | | [3] https://www.amazon.com/Common-Lisp-Condition-System- | Mechanis... | | [4] https://github.com/Apress/common-lisp-condition-system | dreamcompiler wrote: | Just bought it. Thanks for writing this, and thanks for your | continuing participation in discussions of CCL issues on | github. | phoe-krk wrote: | Thank you; I hope that I will find and allocate enough time | in the nearby future to help with maintaining CCL some more. | db48x wrote: | That's a really impressive way of both continuing the | conversation and sneaking in extra improvements to your book! | phoe-krk wrote: | Thanks. The only thing that is sad to me is that this content | did not make it into the book itself. | | (I guess that I can be proud of myself though: the first | edition of the book wasn't even out of production and I | already had some stuff written for the second edition.) | oalae5niMiel7qu wrote: | There's an issue with 4.6.8: You can't use saved images to | debug errors in SBCL, because SBCL's SAVE-LISP-AND-DIE function | throws the stack away. | phoe-krk wrote: | Yes, this last fact is explicitly mentioned in the last two | sentences of this section. | | _> Another issue is preserving the state of the program | stack, as it is destroyed in the process of dumping the Lisp | image, and it cannot be fully restored when the image is | thawed. However, portability libraries such as Dissect[0] | allow for saving the stack state as normal Lisp data that can | then be inspected using the system inspector._ | | [0] https://shinmera.github.io/dissect/ | | Still, you can fully preserve the full stack information as | Lisp data along with the whole heap, dump the image, and then | inspect the dumped core as if you inspected any other Lisp | image. This is already a big quality improvement over digging | in dead crash dumps or - worse - textual, printed stacktraces | with no other information available. | | Therefore, I think that the statement "you can't use saved | images to debug errors in SBCL" is somewhat far-fetched, | really. ___________________________________________________________________ (page generated 2020-10-23 23:01 UTC)