5 ms·
All language keywords are “magic” to some degree. The control flow that try/catch creates has never been surprising or invisible in my experience.
by jtdev 7y ago
All language keywords are “magic” to some degree. The control flow that try/catch creates has never been surprising or invisible in my experience.
- cheeze 7y agoFully agree. IMO this is the same argument as "the for loop is a magic keyword" which just isn't true. Try/catch makes it _incredibly_ clear what you're trying to do. Kids who graduate college and get their first real job understand what a try/catch does. A random contributor to an OSS project knows what try/catch does. Meanwhile, I'd be willing to bet that not even 10% of programmers could actually explain what a monad is. I'm sure there is a place for this elegant error handling, but in most codebases it seems like a pretty big complexification for not all that much benefit. Sure, the code might even be "more correct" (whatever that means), but if Samantha the intern can't pick it up rather quickly, it probably isn't all that well suited for mainstream usage.
- lmm 7y ago> IMO this is the same argument as "the for loop is a magic keyword" which just isn't true. The for loop is indeed a magic keyword, though it's less surprising/magic than try/catch; most of what for does could be done by a plain old function. > Meanwhile, I'd be willing to bet that not even 10% of programmers could actually explain what a monad is. Don't try to generalize prematurely. Considering Either on its own, it's simpler than try/catch and can replace their use cases. If you'd started with Either, try/catch would seem like the overcomplicated solution in search of a problem that it is.
- true_religion 7y agoAlmost all special purpose control syntax can be replaced by a series of function calls, but the value of syntax is clarity of intent.
- cheeze 7y ago> If you'd started with Either, try/catch would seem like the overcomplicated solution in search of a problem that it is. But we didn't start with Either, which IMO is an incredibly important distinction. Bolting things like this onto the language after the fact isn't the same as having first class support by default (like in say, Haskell)
- agentultra 7y agoHaskell doesn't have special support for Either. It's a plain old data structure with a few type classes. You can write Either in any language that supports passing functions as parameter values. It's taking the logical disjunction operator, `||` in many languages, and sticking it in a data structure. You can now pass it around like a normal JS value and combine it with other such values. The benefit of this over exceptions is that for unexceptional situations you don't end up throwing away the context of your computation if something takes the "bad path." Instead of jumping to the exception handler and losing all of your data your program can handle the situation at the site of the error where it has the most context to solve the issue. TFA wasn't advocating abandoning try/catch -- it was suggesting that for non-exceptional cases it will make your code cleaner.
- IanCal 7y agoI think Maybe, Either, etc. are just implemented in regular Haskell, they're not special things in the language.
- Fellshard 7y agoThis argument doesn't hold with me, because while try-catch is very clear in what it does, the exception-/throwing/ mechanism is not. Try-catch is only useful insofar as you know exactly what you're trying to catch. Anything else is basically undefined behaviour; a jump straight to your catch block from anywhere in the application, as far as your intuition is concerned, a far cry from the controlled nature of loops.
- RHSeeger 7y ago> I'd be willing to bet that not even 10% of programmers could actually explain what a monad is. To be fair, I don't think you need to really understand what monad is to understand Either.
- dropofwill 7y agoIt depends on how it is used. I’ve definitely seen it used like a goto mechanism (on the JVM, i don’t write much JS), e.g. throw x in y to skip these business logic steps in z, and that makes my head hurt. Personally I just try not to throw exceptions when compensation/recovery is expected. If something should be handled by the caller put it in the return type, if it’s really fatal give up and log a stack trace.
- lmm 7y ago> All language keywords are “magic” to some degree. Agreed. Every keyword adds complexity to the language; the fewer your language needs, the better. If your language has first-class functions and polymorphism (and any serious language does these days), there's no need for special-case control flow keywords; better to have a design like e.g. Smalltalk, where if/while/... are just normal functions. > The control flow that try/catch creates has never been surprising or invisible in my experience. One of the biggest production bugs I saw happened because of removing an unused variable (the function looked correct, but actually the unused variable right at the top of the function was throwing an exception; the fallback code path was correct, but the function itself was implemented wrong). It's the same problem as https://glyph.twistedmatrix.com/2014/02/unyielding.html https://glyph.twistedmatrix.com/2014/02/unyielding.html - you can't tell which function calls might throw by looking at them, and most of them don't, but some of them do. And on top of that you have the goto-like "action at a distance": starting from a given catch there's no way to find the corresponding throw (or vice versa). You can't even tell when a catch is completely unused.
- Udik 7y ago> Every keyword adds complexity to the language Easily refuted observing that you could strip languages of many of their keywords (for example replacing for and while with if and goto), and you'd end up with less readable code. Keywords are often added to make languages simpler, at least because they declare the intention of the developer. Otherwise, Brainfuck would be the least complex language to write code in.
- lmm 7y agoYou're conflating the complexity of the language itself with the complexity of code written in that language. Brainfuck is a famously simple language; brainfuck codebases are quite complex. Sometimes building complex functionality into a language might be worthwhile, if it allows you to simplify code written in that language. But since a developer working in the language has to be able to understand both the language and the codebase they're working on, you need to be careful about that tradeoff, only adding to the language those features that are general and powerful enough to simplify a lot of codebases. Otherwise you blow the whole complexity budget on language functionality, leaving the developer with no spare mental capacity to understand the specific codebase - even if their codebase makes no or minimal use of some of those complex language features. Better, where possible, to implement general-purpose reusable functionality in libraries written in ordinary code in the language. That's a win-win approach: reusable library code can simplify codebases written in the language, but since it's plain old code that follows the normal rules of the language, a library doesn't add language complexity that the developer is forced to keep in mind. Fundamentally, the key to making codebases in your language understandable is to be compositional: make sure that the developer can easily understand the combination of a and b if they understand a and b separately. Library functions do that, because you can understand how code that calls a library function behaves without needing to understand what the library function does. But language features like throw/catch don't do that: you need to understand what throw does if you are to understand code that calls a function that throws, even if the code you're trying to use doesn't throw itself.