5 ms·
> but maybe they could have spent a tiny bit more of that deep thinking about the problem of error handling First, to understand how to handle errors different
by 9rx 2y ago
> but maybe they could have spent a tiny bit more of that deep thinking about the problem of error handling
First, to understand how to handle errors differently, you have to understand how errors are different.
Like, is a person's age an error? Your gut reaction is almost certainly "What? No. A person's age isn't an error." Yet soon enough you're writing age verification checks like: if age < 18 { /* not an adult */ }. if age < 21 { /* not old enough to drink in the USA */ } – with all the exact same problems if err != nil {} has. Clearly it is an error in certain contexts.
Keep going and you start to wonder what branching situation isn't error handling. So, really, it seems to me what we really want is a better way to express branching operations. "if" is one of the earliest additions to programming languages, so it stands to reason that it is getting a little long in the tooth.
The effort to improve error handling is clearly there. Core team member Ian Lance Taylor submitted a new proposal and built a reference implementation just within the last few months. There have been ~200 error handling proposals! It is a super hard problem, though. A "tiny bit more" thinking is not sufficient.
- maleldil 2y ago> A "tiny bit more" thinking is not sufficient. They could've at least looked at what other languages were doing at the time, and that would have been much better than what they ended up with. However, as with many other aspects of Go, the creators ignored existing work and went with what seemed like improvements to problems from decades ago.
- 9rx 2y ago> They could've at least looked at what other languages were doing at the time Other languages at the time were doing the exact same thing, except, maybe, wrapping the error in a monad such that you have to check the error before doing anything with value, but still with "if" or an if-like construct. But that is unnecessary in Go as it believes that values should always be useful – which means you don't need to even consider the error to use the value.
- int_19h 2y ago> you don't need to even consider the error to use the value That's how you get garbage (but valid) values fed as input to your code, eventually resulting in garbage output produced on the other end of the pipe. Always fun to debug.
- 9rx 2y ago> That's how you get garbage (but valid) values fed as input to your code If you face a function author who doesn't know what the hell they are doing and, as a result, foolishly feeds you garbage, then yes, it is possible for that garbage to propagate unknowingly. But when faced with a function author that doesn't know what the hell they are doing, that's going to be a problem no matter what. No language construct ever conceived can save you from someone who doesn't know what the hell they are doing. They will do foolish things in every language ever created. -- That is not a reasonable position. If a function is written sensibly, the value can never be garbage. How could it be?
- maleldil 2y agoWhat does this even mean? If I read from a file that doesn't exist, what should the returned object be? Isn't the state of the file directly tied to the error? If I ignore the error and try to use the file anyway, the following operations wouldn't make sense. How can you read from a non-existent file?
- 9rx 2y ago> If I read from a file that doesn't exist, what should the returned object be? First things first, how can you read from a file that doesn't exist? Even you questioned that later, so this is a strange question. – Do you mean if you try to open a file that doesn't exist? nil would be a reasonable value. It is how Go signifies the absence of something. nil is always useful and with `if file == nil` will tell you that the file isn't there. No need to observe the error. If you need to know why the file isn't there then, sure, you're also going to have to look at the error. Sometimes that is important. In the case of opening a file you realistically will need to know why it failed to open so that you can resolve the condition, but in other cases you don't need to worry about why it failed. Checking the error would be unnecessary in those cases, assuming the function author has some understanding of how to make even half-decent APIs. Let's assume here, for the sake of discussion, that you have no need to know why the file was unable to opened. Again, how do you envision garbage propagating into the rest of your program?
- elvlysh 2y agoMore like you ignore the existing work that Go itself is built on.
- pas 2y agoCould you elaborate on that, please?
- valenterry 2y ago> So, really, it seems to me what we really want is a better way to express branching operations. There is a fairly wide-adopted and battle-proven pattern. It's called monads. Basically, you put a "context" over parts of your code and then some type of branching becomes implicit. Good languages generalize it and allow their developers to create and use their own contexts. Other languages at least have special cases for very common cases. async/await is such an example.
- 9rx 2y ago> There is a fairly wide-adopted and battle-proven pattern If by fairly wide-adopted you mean it is adopted in a handful of language nobody uses, sure. > Other languages at least have special cases for very common cases. Where they usually screw it up. Look at the canonical example of a "monad" in Rust: let result = divide(3.0, 2.0); match result { Just(x) => println!("Answer: ", x), Nothing => println!("division failed; we'll get 'em next time."), } That is just "if" by another name!
- steveklabnik 2y agoWhere did you get this Rust code from? I’m curious.
- 9rx 2y agoI found the same code in several different places. Wikipedia was the place I ultimately copy/pasted it from.
- steveklabnik 2y agoHuh, I don't see this code on Wikipedia. I ask because it's very much not "canonical," Just and Nothing are Haskell terms, not Rust terms. You'd expect at least Some and None. Regardless, I do agree that in this specific circumstance, you're emulating an if. There's even a form in this case that emulates it even more closely: if Some(x) = divide(3.0, 2.0) { println!("Answer: ", x); } else { println!("division failed; we'll get 'em next time."); } But these simple cases don't show off where Option/Result truly shine.