There's still a few Common Lisp features the mainstream would benefit from ripping off. Relevant here is the condition system. Conditions and restarts are still foreign enough that it's hard to explain to people why the "debugger" was a fundamental part of the lisp machine UI, and how much sense that made, and harder still to fathom how you'd adapt that to Unix, where it would morph into some kind of weird IPC tied into the command shell.
It's not just about being able to stop your program and hot-patch code to recover from errors - this should be trivial in any dynamic language. Rather, it's about being able to composably control how to recover from exception conditions (or, really, any branching behavior) both interactively and programmatically in a unified framework.
There is another class of error that has been poorly implemented since the demise of Smalltalk - exceptions during testing that can be fixed by the programmer and allow the program execution to continue.
In a lot of the non-trivial systems I've worked on, the setup to do debugging/development work to get to a particular part of the code can often be arduous and time consuming, meaning that if you made a mistake and have to restart your debugging session you then have to go through the setup process again which radically slows down progress on thorny issues.
In Smalltalk it was possible to take an unhandled exception, re-inject a more suitable value and continue debugging so that each "session" was far more productive and the setup issue became much less onerous.
Sadly, .NET, Java, Javascript and Python can't even come close to the power of that ability to catch an exception, modify the object and continue execution. It's a great loss in my opinion. So much of the Smalltalk programming environment has been copied into modern languages but not re-entrant exceptions.
Now, while it was very powerful it could also be heavily abused including at run time, but I still miss that ability.
The more I write software, the more I think errors should be first-class citizens (camp #2 from the OP's post).
I've been using https://github.com/biw/enwrap (disclaimer: I wrote it) in TypeScript and have found that the overhead it adds is well worth the safety it adds when handling and returning errors to users.
That said, I see parallels between the debate about typed vs. non-typed errors and the debate of static typing vs. dynamic typing in programming languages.
> I see parallels between the debate about typed vs. non-typed errors and the debate of static typing vs. dynamic typing in programming languages.
Author of the post here. I also see this parallel in error handling discussions. But seems like it's much harder to sell error handling than static typing. Static typing was also much more debatable in the past then now so maybe same can happen to error handling mechanisms in the future as well.
Your project seems very interesting! Typescript is sophisticated enough to model complex Result-like types that can narrow-widen error cases throughout the code. I will check it more if I find the time.
The biggest problem I see is that, like static/dynamic typing, it's usually a boil-the-ocean problem. Most languages have historically been static or dynamically typed. Only recently have TypeScript and Python allowed for migration from dynamic to static typing, introducing millions(?) of developers to static types in the process.
With errors, it's hard since many languages can throw errors anywhere, so it's hard to feel like any function is "safe" in terms of error handling. That's one of the reasons why `enwrap` returns a generic error alongside any other result: to support incremental adoption.
If you have a chance to check out `enwrap` and have feedback, email me! (link in bio)
I don't agree with the criticism of Java's exception handling, since Java literally does make exactly the distinction between "1. A Bug in the system" as RuntimeException, and "2. A faulty situation that can't be avoided" as all other Exception, particularly IOException.
Although the same statement is used to catch both, you only catch both if you catch Exception. If you catch IOException or whatever other exceptions you need to catch and you opt to handle them, then RuntimeException will still propagate.
It is only a matter of understanding that this is the distinction, and writing the bodies of your catch blocks accordingly.
And if you are only writing a prototype, declare that it throws Exception, or if you can't, a catch (IOException ex) { throw new UncheckedIOException(ex); } really isn't that bad.
There's still a few Common Lisp features the mainstream would benefit from ripping off. Relevant here is the condition system. Conditions and restarts are still foreign enough that it's hard to explain to people why the "debugger" was a fundamental part of the lisp machine UI, and how much sense that made, and harder still to fathom how you'd adapt that to Unix, where it would morph into some kind of weird IPC tied into the command shell.
It's not just about being able to stop your program and hot-patch code to recover from errors - this should be trivial in any dynamic language. Rather, it's about being able to composably control how to recover from exception conditions (or, really, any branching behavior) both interactively and programmatically in a unified framework.
There is another class of error that has been poorly implemented since the demise of Smalltalk - exceptions during testing that can be fixed by the programmer and allow the program execution to continue.
In a lot of the non-trivial systems I've worked on, the setup to do debugging/development work to get to a particular part of the code can often be arduous and time consuming, meaning that if you made a mistake and have to restart your debugging session you then have to go through the setup process again which radically slows down progress on thorny issues.
In Smalltalk it was possible to take an unhandled exception, re-inject a more suitable value and continue debugging so that each "session" was far more productive and the setup issue became much less onerous.
Sadly, .NET, Java, Javascript and Python can't even come close to the power of that ability to catch an exception, modify the object and continue execution. It's a great loss in my opinion. So much of the Smalltalk programming environment has been copied into modern languages but not re-entrant exceptions.
Now, while it was very powerful it could also be heavily abused including at run time, but I still miss that ability.
You can do this in a good Java debugger like IDEA. Break on the throw, unwind stack frames to your liking, hot-replace code, continue.
Do any of these languages have anything similar to Ruby's retry within error handling?
The more I write software, the more I think errors should be first-class citizens (camp #2 from the OP's post).
I've been using https://github.com/biw/enwrap (disclaimer: I wrote it) in TypeScript and have found that the overhead it adds is well worth the safety it adds when handling and returning errors to users.
That said, I see parallels between the debate about typed vs. non-typed errors and the debate of static typing vs. dynamic typing in programming languages.
> I see parallels between the debate about typed vs. non-typed errors and the debate of static typing vs. dynamic typing in programming languages.
Author of the post here. I also see this parallel in error handling discussions. But seems like it's much harder to sell error handling than static typing. Static typing was also much more debatable in the past then now so maybe same can happen to error handling mechanisms in the future as well.
Your project seems very interesting! Typescript is sophisticated enough to model complex Result-like types that can narrow-widen error cases throughout the code. I will check it more if I find the time.
The biggest problem I see is that, like static/dynamic typing, it's usually a boil-the-ocean problem. Most languages have historically been static or dynamically typed. Only recently have TypeScript and Python allowed for migration from dynamic to static typing, introducing millions(?) of developers to static types in the process.
With errors, it's hard since many languages can throw errors anywhere, so it's hard to feel like any function is "safe" in terms of error handling. That's one of the reasons why `enwrap` returns a generic error alongside any other result: to support incremental adoption.
If you have a chance to check out `enwrap` and have feedback, email me! (link in bio)
I don't agree with the criticism of Java's exception handling, since Java literally does make exactly the distinction between "1. A Bug in the system" as RuntimeException, and "2. A faulty situation that can't be avoided" as all other Exception, particularly IOException.
Although the same statement is used to catch both, you only catch both if you catch Exception. If you catch IOException or whatever other exceptions you need to catch and you opt to handle them, then RuntimeException will still propagate.
It is only a matter of understanding that this is the distinction, and writing the bodies of your catch blocks accordingly.
And if you are only writing a prototype, declare that it throws Exception, or if you can't, a catch (IOException ex) { throw new UncheckedIOException(ex); } really isn't that bad.
Rust does a great job here. I know it is based upon other languages but error handling in Rust doesn’t suck