[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)