There are no stdlib filter/map/reduce yet, and generics are new enough that most people are still writing the same for-range loops over and over. Lambdas don’t capture loop variable values.
x, y := f() sometimes assigns and sometimes shadows an outer scope.
defer isn’t block scoped, you have to simulate the code to figure out which calls are stacked at this point. Hopefully they’re added deterministically, but you can keep deferring stuff in a loop if you want.
(This is aside from the many ways Scala and Kotlin are far more readable than Java.)
How do you easily ignore the majority of the screen? Is there an editor plugin that just doesn’t display those ifs?
Here's one example that might make it clearer: https://commandcenter.blogspot.com/2017/12/error-handling-in....
If the only thing you're doing is saying `if err != nil { return nil, err }` anyway, which is what empirically the majority of go's error handling code does, yes, you should have a better way of representing that, whether it's a "?", ASSIGN_OR_RETURN, or exceptions, which take a single character, a single line, or a no characters at all, as opposed to golang's 3 lines of visual noise!
Saying that "you can use custom error types to give finer grained error information" is great and all, but I learned that in my CS101 class, in Java, in like 2012. It's not fancy or better or good, except compared to C. Its a worse way of doing a thing that Java and Python (and...) have been doing for decades.
// Bad, most of the time.
result, err := foo(x)
if err != nil {
return err
}
But to do this: // Better, most of the time.
result, err := foo(x)
if err != nil {
return fmt.Errorf("performing foo on x = %v: %s", x, err)
}
This isn't noise. This is meaningful logic. That is what exception mechanisms seem to discourage, as they are invisible (unless you wrap every call to everything that throws into a try{}, but then it's not better than an if{} is it?), and this is what the ? operator in some languages discourages as well, because it adds no human- or machine-readable context.There must be syntax that indicates what to do when an error occurs, and that syntax must support addition of information about the context in which the error has occurred. And why not have the most basic conditional take the role of that syntax?
Also, bubbling up is the sane default, even if you are writing a fast script/trying out some code it won’t clutter anything and you will be notified to handle the problems once you want to write robust code. While in Go you will just put empty if errs and will forget about the whole thing, swallowing errors which are arguably the worst thing.
try { let res = somethingThatMightFail(); catch(err) { // log the error or something return null; }
// res is good now... but it's out of scope
Whereas in C++ you'd just write
ASSIGN_OR_RETURN(auto res, somethingThatMightFail())
And it will bubble your error status just like you wanted, only takes 1 line of code, and keeps all the error information (assuming you're using absl::Status).
I haven't really used Go, but it sounds like the worst of both worlds.
> even if you are writing a fast script/trying out some code it won’t clutter anything and you will be notified to handle the problems once you want to write robust code
How are you going to go back and find every callsite that might potentially fail at a later date? There's no easy way to even tell if somethingThatMightFail might fail if it doesn't return something like a "StatusOr" or "Option" or something. I think Java has some tooling to force exception handling. Not sure about Go.
While Java’s exceptions are not without problems (too deep inheritance trees don’t make sense on exception types, lambdas not operate too well with this feature, etc), I think it is a really good direction not utilized to the full by any mainstream language.
I've seen people that do that and it sounds nice until you end up with
Failed foo on x: failed bar on x: failed doing baz on x: failed to do quux".
You've created a worse stacktrace! I now have to search for a string and hope I can guess where the %s (or v or q) was instead of just grabbing the line number.
Or I do none of that, pass errors around directly, and just use the stacktrace.
The issue with your claim is that we only care about errors in two places, where we create them (like where the file was unable to be read) and where we handle them (when they are either swallowed bc the behavior was expected or passed to the user).
You don't care about them in all the intervening code, and exceptions handle that better.
> And why not have the most basic conditional take the role of that syntax?
Because it bloats your code and you end up with in practice more lines spent on verbose, cookie cutter error returning than actual logic!
Like, you don't need additional human readable context except in very particular cases. Go proves this in practice as almost everyone just returns the unmodified error most of the time, and it works great. Except for the annoying syntax.
Because having approx. 3/4 of your code devoted to error handling and error propagation is both hard to write and hard to read.
IMO rust's approach is close to ideal, in the common case where you just propagate up, the syntax is succinct, but still explicit, and when you handle it, the logic is close to the callsite, and is just working with a normal value. Useful stack traces are still a bit of a problem, but there is work being done on that.
And that's not to mention how in go you always return both the error and regular values, even though you usually only use one.
I agree about defer and the walrus operator though. Both can be quite confusing at times, and I think that Go with block-scoped defer and no walrus would be a more consistent language.
2) No try/catch. Inexcusable in my opinion.
3) The syntax for declaring maps/arrays looks gross. Again, being different for the sake of it.
4) No while loops. The for loop syntax is not too bad, but I think this was again a choice made out of trying to simplify things when not a single person ever complained that "Java/C has too many types of loops".
I could go on, but in general my opinion is that Go deviated from some good established norms for no good reason, and this makes it much harder to adopt. It also isn't fun when you have a code base with e.g. Go and Java or Go and C# mixed in. The mental overhead of having an entirely different syntax to reason about is frustrating. And for what benefit? At least list comprehensions in Python are nice to read. I can't think of anything that is nice to read in Go.
You won't like Rust either then. Though its just a matter of time till Rust gets try-catch too. It's slowly moving towards that step by step. Folks have already made crates https://docs.rs/try-catch/latest/try_catch/
For 2 you can see it that way. Or you are glad there are no exceptions and prefer the explicitness of error handling in Go. So in the end it's also subjective I guess.
I remember when I first looked at Go ~7-8 years ago I also didn't like the syntax. However it grew on me and now I like it. Opinions change. These days I try to not instantly judge something on my initial feelings.