4 ms·
> why don’t more languages have them? My guess would be that they are not needed. Exceptions for anything other than program death are something of an anti-pat
by jahewson 14y ago
> why don’t more languages have them?
My guess would be that they are not needed. Exceptions for anything other than program death are something of an anti-pattern, potentially spreading the internal details of some piece of code all the way up the stack.
My choice of implementation would be to pass a discriminator function which takes an error and returns true if the error is recoverable. Then you have the choice of either returning all the errors at the end or reporting each earlier when the discriminator is called.
Edit: That's not to say that exceptions can't be used effectively, they just mostly aren't.
- brudgers 14y agoMy understanding of Seibel is that Lisp's condition system is designed to avoid exactly the problem of internal details spreading up the stack. It does so by allowing conditions (of which exceptions are one type) to be handled by a higher level of the call stack without unwinding the call stack, and allows for the determination of which particular condition handler to be made independently (and at a higher level of the call stack) than the implementation of the handler. The "anti-pattern" may be a result of the way in which other languages implementation of exception (or condition) handling differs from that of Lisp.
- jahewson 14y ago> [conditions are] handled by a higher level of the call stack without unwinding the call stack, and allow for the determination of which particular condition handler to be made independently (and at a higher level of the call stack) than the implementation of the handler. Is exactly what the higher-order function I described would let you do. Why do we need conditions? Edit: the answer to this question is "because we want to use exceptions". But there's really no need to.
- brudgers 14y agoPer Greenspun's Tenth Rule you do not. http://paulgraham.com/quotes.html http://paulgraham.com/quotes.html
- masklinn 14y ago> Is exactly what the higher-order function I described would let you do. Why do we need conditions? Because conditions let you do that correctly without e.g. changing all intermediate pieces of code so they forward some sort of gigantic mapping of callables through the stack.
- jahewson 14y agoThat's non-essential, but certainly nice to have. Thanks for the insight.
- ScottBurson 14y agoThat is, of course, the standard argument for dynamic binding in general. So in CL one could certainly pass a callback via dynamic binding and get the same effect. The choice to use the condition primitives instead is, then, simply a stylistic one.
- Someone 14y agoFor those who wonder whether that is worth a new idiom to learn: the stylistic effect is enormous. For example, there could be 5 layers of code between a condition handler and the code signaling a "file not found" condition, and the top levels of that stack might not even be aware that they could trigger such a condition or even any condition at all. In some sense, this is the reverse of Java's checked exceptions. There, you have the problem that an interface declares exception X and Y, and an implementation that wants to throw a Z has to encapsulate that exception into a X or an Y. Here, the intermediate layers _can_ be blightfully unaware of those conditions. A common use case is to use conditions without registered handlers as "break on exception" breakpoints, with the benefit that the human working with the debugger has more options than "stop execution", "continue and pray" and "tweak some memory, set the PC, go, and hope for the best". For example, if the 'open file' system call has a restart, users can easily fix "oops, that lengthy computation is done, but I mistyped that output file name." error conditions.
- brudgers 14y ago
- stray 14y agoLisp's condition system allows you to handle the condition without unwinding the stack. That's the whole point.
- jahewson 14y agoSo does a higher-order function... Therefore it can't be the whole point, because that mechanism already exists, which most commenters here seem to have not realised. The actual point is, that it gives you more powerful exceptions, but you don't actually need them.
- ajuc 14y agoNot the same, because you need to pass function to be called on error throught all the stack, and you don't need to do it with conditions, if I understand correctly. Example: main calls calculateSth calls f1 calls f2 calls f3 calls readFiles If we want to decide in main how we want to react to errors when reading files, we need to pass the function to handle errors thorught the whole chain. With conditions calculateSth,f1,f2 and f3 don't need to know about our condition. This also means that when you add new condition to function that you invoke in many places, you need much less changes everywhere.
- jahewson 14y agoAt last, an actual answer, thanks. Basically, we can save the need to pass a callback argument.
- masklinn 14y agoAnd the potential need to pass way more than one, if you have 5 different error conditions which can exist in the same stack you need to come up with some sort of standard way to express that (e.g. a mapping of condition to callable). Then you need to find out how to express inheritances/overrides, and the like.
- 14y ago
- mschaef 14y ago> My choice of implementation would be to pass a discriminator function which takes an error and returns true if the error is recoverable. Conditions are close to exactly that + syntactic sugar. `handler-bind` lets you establish a dynamic scope, inside of which specific functions are called when specific conditions are signaled. Signaling a condition = call the first handler function in the list that handles the signaled condition. The handler is called before the stack is unwound, which allows it to elect to either `throw`, invoke a restart, or ignore the condition entirely. This is in contrast to exceptions in (say) Java. `throw new FooException()` does a lot of potentially destructive work before control gets transfered to the exception handler: It allocates an exception object, which fully captures the current thread's stack. (It's not too common, but this can be bypassed, if you don't throw a new exception object...) It unwinds the stack to the frame that has a matching `catch`. Only after those two things have happened does the exception handler get a chance to decide how to proceed, and at that point, it's too late for many recovery strategies. Conditions help this by making it easier to intercept the process earlier. Where they hurt is that they add another level of complexity to API design. With Java style exceptions, if the function fails, it fails completely. There's a convenient simplicity to that. With Lisp style conditions and restarts, A function will let you know how things are going as it does its work and then give you the opportunity to change its behavior midstream. It's a lot more work to design that kind of API, and it's probably well past the point of diminishing returns for most kinds of development.
- jahewson 14y agoGreat answer. I'm left wondering if the solution is more complex than the problem.
- lispm 14y agoThere might be two different perspectives on this: * automated error handling, mostly invisible to the user. In this case the condition system may give an attractive architecture, but it is not widely explored and it is not clear how it would integrate into a language like Java. There could be a benefit, but it is not explored by software architects. Apple's Dylan probably had something to it, but it was not widely used. * error handling in interactive systems. That's where the Lisp community used/uses it mostly. The largest effort to use the condition system were in the OS of the MIT Lisp machine descendants - especially the one from Symbolics. It was used both in the development environment - which is tightly integrated in the OS. Most of the development tools were using it. But things like the networking code or the file system were also using it. The user interface is generally slightly more complex than current graphical user interfaces and it was expected that the user used this kind of power. When you get a dialog of restarts presented, the user needs more thinking about how to proceed than with a simple cancel/abort dialog box. There were also applications written using it and for that slightly simpler interfaces were used. For example the restarts were presented in dialogs and not in a debugger context. It takes a bit of re-orientation to imagine a user interface, where the user has the option to repair/retry a failed operation, instead of the usual abort/redo. These benefits are easier to get in an integrated system (-> Lisp, Smalltalk) - a model where several non-integrated tools interact in a condition system is more difficult to imagine. Today, most of the better Common Lisp systems are using the condition system in their environment. There the power can be used, but at the same time it is also easy to ignore and just use a default mode of using the abort restart - without getting the benefits of thinking a bit more and investing the time to use a more complex restart.