Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I think its more of a comment that "err != nil" is used in the vast majority of cases, so you start to treat it as noise and skim it.


That reality may make the fundamental flaws of the if statement more noticeable, but at the end of the day the problem is still that the if statement itself is not great. If we're going to put in effort to improve upon it – and it is fair to say that we should – why only for a type named error?


Because the type named error is used in that flawed way orders of magnitude more than any other type. If there were other types that were consistently used as the last return value in functions that short-cirucuited when calling other functions that retuned specific sentinels in their final value when called, there would be reason to do it for them too.

In fact, this is exactly what Rust's ? -operator already does, and something that's obscured by the oddness of using pseudo-tuples to return errors alongside non-error values rather than requiring exactly one or the other; `Result` in Rust can abstract over any two types (even the same one for success and error, if needed), and using the ?-operator will return the value from the containing function if it's wrapped by `Err` or yield it in the expression if it's wrapped by `Ok`. In Go, the equivalent would be to have the operator work on `(T, E)` where `T` and `E` could be any type, with `E` often but not always being an error. Of course, this runs into the issue of how to deal with more than two return values, but manually wrap the non-error values into a single type in order to use the operator would solve that with overall way less boilerplate than what's required currently due to it being rarely needed.


> Because the type named error is used in that flawed way orders of magnitude more than any other type.

That does not give reason to only solve for a narrow case when you can just as well solve for all cases.

> If there were other types that were consistently used as the last return value in functions that short-cirucuited when calling other functions that retuned specific sentinels in their final value when called, there would be reason to do it for them too.

Which is certainly the situation here. (T, bool) is seen as often as (T, error) – where bool is an error state that indicates presence of absence of something. Now that your solution needs to cover "error" and "bool", why not go all the way and include other types too?

Errors are not limited to "error" types. Every value, no matter the type, is potentially an error state. bool is an obvious case, but even things like strings and integers can be errors, depending on business needs. So even if you truly only want to solve for error cases, you still need to be able to accommodate types of every kind.

The computer has no concept of error. It is entirely a human construct, so when handling errors one has to think about from the human perspective or there is no point, and humans decidedly do not neatly place errors in a tightly sealed error box.

> rather than requiring exactly one or the other

That doesn't really make sense in the context of Go. For better or worse, Go is a zero value language, meaning that values always contain useful state. It is necessarily "choose one or the other or both, depending on what fits your situation". "Result" or other monadic-type solutions make sense in other languages with entirely different design ideas, but to try and graft that onto Go requires designing an entirely new language with a completely different notion about how state should be represented. And at that point, what's the point? Just use Rust – or whatever language already thinks about state the way you need.

> but manually wrap the non-error values into a single type in order to use the operator would solve that

I'm not sure that is the case. Even if we were to redesign Go to eliminate zero values to make (T XOR E) sensible, ((T AND U) XOR E) is often not what you want in cases where three or more return arguments are found. (T, bool, error) is a fairly common pattern too, where both bool and error are error states, similar to what was described above. ((T AND U) XOR E) would not fit that case at all. It is more like ((T XOR U) OR (T XOR E)).

I mean, realistically, if we completely reimagined Go to be a brand new language like you imagine then it is apparent that the code written in it would look very different. Architecture is a product of the ecosystem. It is not a foregone conclusion that third return arguments would show up in the first place. But, for the sake of discussion...


> That does not give reason to only solve for a narrow case when you can just as well solve for all cases.

...

This clearly can't be solved "just as well" because nobody can figure out how to do it. The second half of your comment alludes to this, but a lot of what makes this hard to solve are pretty inherent to the design of the language, and at this point, there's a pretty large body of empirical evidence showing that there's not going to be a solution that elegantly solves the issue for every possible theoretical case. Even if someone did manage to come up with it, they're literally saying that they wouldn't entertain a proposal for it at this point! I don't understand how you can come away from this thinking it's realistic that this would get solved in some general way.

> The computer has no concept of error. It is entirely a human construct, so when handling errors one has to think about from the human perspective or there is no point, and humans decidedly do not neatly place errors in a tightly sealed error box.

That's exactly the argument for solving this for what you're calling a "narrow" case. Providing syntax just for (T, E) that uses the zero value for T when short-circuiting to return E would improve the situation from a human perspective, even if it meant that to utilize it for more than two return values you need to define a struct for one or both of T or E. The only objections to it that you're raising are entirely from the "computer" perspective of needing to solve the problem in a general fashion, which is not something that needs to be done in order to alleviate the issues for humans.


> This clearly can't be solved "just as well" because nobody can figure out how to do it.

Fine, but then that means there is no other solution for Go unless you completely change the entire fundamental underpinnings of the language. But, again, if you're going to completely change the language, what's the point? Just use a different language that already has the semantics you seek. There are literally hundreds of them to choose from already.

> That's exactly the argument for solving this for what you're calling a "narrow" case.

Go has, and has had since day one, Java-style exception handlers. While it understandably has all the same tradeoffs as Java exception handling, if you simply need to push a value up the stack, it is there to use. Even the standard library does it when appropriate (e.g. encoding/json). The narrow error case is already covered well enough - at least as well as most other popular languages that have equally settled on Java-style exception handling.

Let me be clear: It is the general case across all types that is sucky. Errors, while revealing, are not the real problem and are merely a distraction.


I assume with "Java-style exception handlers" you're referring to panics? If this were sufficient, then people would be using it more for error handling. I'd argue that a large part of why people don't write code using them as error handling more often is because of the syntax, and that's ultimately what this whole discussion is for me. This is probably another area where we just fundamentally disagree, because I don't really consider there to be a necessity to fully solve the "real problem" instead of providing something smaller that alleviates specific minor warts.

What's strange to me is that the main reason this seems like the best path forward to me is because it doesn't require large fundamental changes to the language, and I'm skeptical that there's any fix to the underlying issues that you're concerned about that wouldn't require those sorts of changes, but somehow your objections seem to mostly be on the grounds that you also don't want those types of changes. To me, the insistence the entire real problem needs to be solved for anything to be worth doing is in practice incompatible with the requirement not to change the language fundamentally, so the question becomes whether its worth considering changes that don't fully solve what you consider to be the real issue. I think that's where the disconnect is; I'm not really trying to argue for a solution to the problem you're concerned about because I don't consider it to be realistic that it will ever get solved, so I'm arguing that making smaller changes to reduce the impact of the problem without solving it fully would be worthwhile compared only to the status quo rather than some ideal solution that I don't think exists. I'm not trying to say that it's an absolute certainty that there's no way to fix the issues you're concerned about without fundamentally changing the language, and I'd be just as happy as you if I turn out to be wrong! It doesn't really feel like you're rebutting the actual suggestion I'm making though because you're interpreting my claim that there isn't any way to solve the problem generally without changing the language fundamentally as my advocating for those fundamental changes, which is not what I actually think, and from my perspective isn't what I've been saying at all.


> I'd argue that a large part of why people don't write code using them as error handling more often is because of the syntax

I don't see meaningful difference in the syntax as compared to other languages with a similar feature: https://go.dev/play/p/RrO1OrzIPNe How deep are we really going to split hairs here?

If it were commonly used you could clean it up a little, like how the somewhat recently added iterators feature cleaned up what people were already doing with iteration, but in this case since it is so rarely used in the first place, why bother? "If you build it, they will come" is Hollywood fantasy. Unlike this, the use of iterators was already prevalent before the iterators feature was added.

Let's be honest: If it were useful, people would already put up with the above being slightly less than perfect. People will actually put up with a lot of shit when something is useful at its core! But that they are doing this almost never is quite telling.

> What's strange to me is that the main reason this seems like the best path forward to me is because it doesn't require large fundamental changes to the language

Or maybe no changes at all. Would using the above really be so bad from a syntactical point of view? The much bigger problem, and why pretty much all modern languages have moved to returning errors as the defacto solution, is that it exhibits all the same fundamental problems as errors under Java exception handling. That is something syntax cannot fix.

And, well, for the exceptional (pun intended?) cases where Java-style exception handling really its the best option to suit your circumstances: It's there to use already!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: