It turns out that that complex technology was warranted?!?
It turns out that that complex technology was warranted?!?
Edit: Reminds me of this funny video https://youtu.be/-AQfQFcXac8?t=63
But I do actually try to minimize them, to the extent that's reasonably possible. And will especially try to minimize "else if" and "else", when reasonable, since I find it increases the cognitive load when reading something.
Interfaces are a better comparison. Interfaces are very useful and I've often used them with great success, but with generally see if other simpler "more direct" solutions work before using interfaces, as those solutions typically less abstract and easier to understand. I've definitely seen some code with "interface overuse" where it could be quite hard to find out what "foo.InterfaceMethod()" actually really does.
It’s worth noting that Go vetting tools do capture a few instances of “misusing” ‘if’ statements so taking your comment sincerely (which is more than it deserves imo), your worst case scenario is already covered.
I don’t want if-statements at all in my code, if-expressions at the most, but they are strictly worse than pattern matching anyways.
The great thing about our current era is that you have more choices in languages and compilers than you have hours in your life to learn them all. So you can be as fussy as you want and there will still be an ecosystem out there for you.
There wasn’t nearly this many choices when I started out in IT. And most of the options that weren’t even free.
You said it would be a showstopper for most people, suggesting that a lack of if-statements is a fundamental problem that needs to be worked around.
I just showed that there exists serious, non-absurd arguments against if-statements, namely that they are strictly inferior to if-expressions, which again are strictly inferior to pattern matching, since they significantly reduces boolean blindness.
At some point (or rather for over a 1000 years) “most” people computed π by polygon approximation, but it turns out that method has severe limitations that more modern methods avoid. Should we still have chosen the method that “most” people were familiar with, over applying infinite series to the problem?
It's the equivalent of putting your head in the sand and pretending nothing else in the world exists.
Error handling in other languages is like confetti. “Pop! Surprise!” A disorganized game of “52 pickup” using an under designed grab bag of control structures. No thanks.
Go asserts that "error handling" and "main flow" (a.k.a. "happy path") code paths are equivalent, and that you can't ignore the bad stuff as you program against the good stuff. Error handling _cannot be_ an afterthought -- this is the path to fragile, even broken, code. If a programmer can't adapt to this model, then they're gonna have a bad time with Go, because with Go it's non-optional!
The compiler is one way that language invariants can be expressed and enforced. But it's not the only one -- another "layer" is convention and code review.
Concretely, something like
v, _ := f(...)
would _never_ pass code review at any reasonable organization.If you use the compiler to express invariants like this, then the language unavoidably becomes more complex. In some contexts, that makes sense! In others, it doesn't.
Ignoring the second return value is also the easy case to catch. Ignoring an error-only return from a function doesn't produce a warning, and won't stand out in code review unless you adopt a standard of never writing void functions.
To be fair, that is a good principle to follow ;) Non-void, especially pure, functions are much easier to understand in the context of an entire codebase.
> Ignoring an error-only return from a function doesn't produce a warning,
It does if you use the standard set of linters, which with Go you almost certainly do. Go has a strong linting culture, in part due to one being built-in. By the way, in vscode said standard set of linters is one click away in the config.
---
There are only 2-3 cases that I know of where failing to handle an error passes by silently. They are edge cases, not something you'll run into writing everyday code.
Go is a language for programminging "in the large" -- it explicitly defers a lot of stuff to layers "above" the compiler, like code review. If you don't buy that as legitimate that's fine!
> if you want to write happy-path code which ignores errors . . .
. . . it's obvious that's what you're doing in code review, and it's trivial to correct. Right?
---
> if you want to write happy-path code which ignores error
If you're doing this consistently you're going to have a lot of `value, _` i.e. underbar assignments in your code, and `_` isn't passing any code review process in any reasonable Go shop I've ever seen.
> Go asserts that "error handling" and "main flow" (a.k.a. "happy path") code paths are equivalent, and that you can't ignore the bad stuff as you program against the good stuff. Error handling _cannot be_ an afterthought -- this is the path to fragile, even broken, code.
Go doesn't assert anything about error handling. Go itself is extremely unopinionated about error handling. If you want to do something with the errors it returns you can, but if for some reason you want to "ignore the bad stuff as you program against the good stuff" it won't try to get in your way.
Idiomatic Go never just drops errors on the floor and ignores them, but it does involve a lot of just bubbling errors upwards without spending any time thinking about what happens in the error path. It's very easy to just write `v, error := foo() if error != nil { return error }` without considering if everything's actually in a sensible state if you return here or if bubbling the error up is even the correct thing to do. It _is_ the correct thing to do so often that anyone reading the code is also going to just glance over it.
If a programmer doesn't spend time thinking about error handling, Go does nothing to encourage them to do so. If they do, Go offers very little to help them do so. It certainly compares well in that regard to C and languages where every expression can throw an exception, but that is a very low standard.
The compiler will let you do this, but reasonable code review won't.
> Idiomatic Go never just drops errors on the floor and ignores them, but it does involve a lot of just bubbling errors upwards without spending any time thinking about what happens in the error path. It's very easy to just write `v, error := foo() if error != nil { return error }` without considering if everything's actually in a sensible state if you return here or if bubbling the error up is even the correct thing to do. It _is_ the correct thing to do so often that anyone reading the code is also going to just glance over it.
Not true? Keeping error handling in the same frame of reference as happy-path code makes programmers consider the two things equivalently, as peers. `if err != nil { return err }` is _not_ idiomatic Go! At a minimum you annotate the error. And that's often enough to be the right thing to do.
> If a programmer doesn't spend time thinking about error handling, Go does nothing to encourage them to do so.
Simply lifting error-path code into the same visible control flow as happy-path code is by itself a huge win for making programmers think about error handling. If you don't agree, fine. Many many others do.
Yes...
> and that you can't ignore the bad stuff as you program against the good stuff.
No? I don't claim that -- at least in the sense that the compiler permits you to ignore errors if you want to. But that's not some death blow against the language. The compiler is one of many mechanisms that can be used to assert code quality.
Go's method of treating them like any other value is the most honest approach I've seen. No one freaks out when a FizzBuzz program does different things with different categories of integers.
If your past experience involved people comparing error.Error() strings I’m sorry. That’s awful and I can see why that would be a nightmare
Not having generics isn't a pragmatic choice per se
I don't think many people would call Java 1.5 or C# 2.0 super complicated.
Do you know what Java 1.5 and C# 2.0 had in common, that Go didn't? Generics.
For reference:
- Java 1.5 - release date: September 30, 2004
- C# 2.0 - release date: January 22, 2006
That's ~18 years ago and 16 years ago, for whoever's keeping track.
They did the same for C#, where 2.0 was a breaking change from 1.1.
I still think Go should have bitten the bullet earlier. Though I'm still glad they're doing it now. Just that now there's a ton more code and they're going to divide the ecosystem until adoption is widespread, from the stdlib to every minor corner of the ecosystem that needs it.
What breaking change? They implemented type erasure specifically not to break existing code.
Adding them later is such a big change that touches so much code you can date it into pre generics/post generics:-)
By generics "on their own", I mean essentially the Hindley-Milner type system, which supports generics and can do static type inference in linear time: https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_sy...
Somehow, the "generics wars" in Go made generics into the scapegoat of programming language complexity, when perhaps it was Go's "interfaces" feature all along. Maybe the enemy was here the whole time!
That has never been the source of contention, and the benefits were noted from the initial discussions of the language before it reached an audience. Like all things it's a trade off. Will you be providing more value with generics, or are other features a higher priority. That's obviously a matter of perspective.
Go has many advantages over other languages, and strict typing, generics and raw speed has never been part of that. If those things are paramount for you, there are better languages suitable.
Go has been fantastic for the problem space I'm targeting. Fast enough, efficient enough, portable enough, simple enough that hiring is easy. This might not be true for what you work on, but I'm not writing a video decoder.
It kind of was though.
It was endless arguments from Go users that generics aren't needed and can be solved through a combination of cut/paste, code generation or simply dismissing its usefulness altogether. And on the other side the rest of us trying to explain the many, many use cases that demand generics.
There was (and still is) a small minority of Go users that insist generics are bad, but in my observation it's nowhere near the majority view. As a concrete datapoint, the GitHub issue on this[1] has 2k upvotes and 150 downvotes. A sizeable minority, but a minority nonetheless.
As is often the case, "the Go community" is far from heterogeneous, and it's hard to really get an idea what "the average Go programmer" thinks, and easy to criticise the most extreme elements.
Certainly for the Go team itself the position was always that 1) generics are useful, but 2) we're not quite sure how to best implement them. Some people disagree with that viewpoint too, which is fair enough, but it's really quite a different one.
I've never seen anyone argue that generics aren't a useful concept, but I've seen plenty argue that it isn't worth the cost in incurs. That is a quite a different debate then the one you are outlining.
The other "plenty" were arguing that it was worth the cost.
So color me surprised that this side was right.
That put a huge burden of proof on the Golang designers as the design space had been studied extensively for 3+ decades. And they skirted around that burden of proof for a while, I never liked their reasoning.
They could have just come out and said the didn't like generics and don't need them for their use cases.
Zig and Rust are in whole 'nother ballgame so there's no point involving them here.
The bottomline is that it's never fruitful to argue that feature sets that go into "simplicity" do not heavily depend on subjective programmer preferences, their tasks, and the intended application domains. Programming languages are mere tools, nothing else. Choose the one appropriate for your goals.
Well, kind of. But in practice, you have to agree that even theoretically "general purpose languages" either fall or get pushed into niches.
For example, Ruby. In real life, mass usage, Ruby is at this point a 1-trick pony, web apps with Rails. While Rails was the rage, it tried and did break out into DevOps, but all those efforts have kind of fallen by the wayside (Chef - a shame, really, Puppet, etc).
Another example, Rust is a general programming language, but few people will actually use it for Line of Business GUI apps, for example. It's just too hard to get started with it and there's no point, you gain too little from its advantages to work so hard through that learning curve.
Same thing for Java and C#. Most companies that will hire you to write something in those languages will probably do it for backend services, plus Java Android apps because Google chose it for that (and then chose Kotlin) and C# Windows apps (but how many people are really writing those these days?). For Go almost everything I've seen is also for backend services.
So in day-to-day life they are direct competitors. Kudos to Go that it got this far and we're putting it into the same league with Java and C#, it's quite an achievement!
I do agree with your principle but in practice most projects are brownfield and your tools have already been chosen by someone else :-)