I disagree (I'm from the Elixir/Erlang "code the narrow happy path and accept failure as quickly as possible" camp) and want to see your empirical data that discarding exceptions was a good design decision. Because to me it seems like you would spend a lot of time writing unnecessary boilerplate code to check for errors everywhere (if you bother checking at all... and if you don't, you are begging to get into an inconsistent runtime state which could lead to extremely hard-to-trace bugs)
It is my informed opinion that all unexpected states are bad and should be logged and killed/restarted from a known state ASAP. (Basically, the Erlang philosophy.)
Here is my data: Here are the most-used words in Go https://anvaka.github.io/common-words/#?lang=go ("if err != nil return err," anyone?) and here are the most-used words in Elixir https://anvaka.github.io/common-words/#?lang=ex . Sure seems like a lot of Go code is written to handle these "exceptions" they're not having! ;)
I didn't actually come here to debate Go, but statements like yours without any evidence to back them up are basically a religious belief and absolutely nothing more.
If you were selling this language to me, how would you explain writing 33% more code to do the same business logic, just to handle errors, over something like Elixir with its rather astonishing uptime and response rate and almost no boilerplate error-handling code? For the backend of an API or webserver, hypothetically.
In either case, you should be handling errors.
Just found this comparison which is at least interesting: https://www.slant.co/versus/126/1540/~golang_vs_elixir
Compared to Java I have ported to Go, returning error + defer has led to much more localized (and this grokable) and less verbose error handling. Less verbose? I suppose this is not true if you throw exceptions and handle them all at the top (Go has panic for these kind of fatal events BTW), but if you regularly have try-catch-finally blocks, the Go approach is in fact less verbose.
Go should have had Rust-style enums that let you do things like Option<T> and Result<T,E>. This way, you are forbidden by the compiler from ever extracting an invalid value from a function that returned an error (or returned no value).
I really don't see a downside of Rust's approach compared to Go's after having programmed in both. But, then again, Rust's approach requires generics and I know Go's team doesn't want to go in that direction.
Nothing in Rust (or Go, even) ordinarily precludes the type of retry approach you're describing, of course.
I disagree:
https://github.com/golang/proposal/blob/master/design/15292-...
https://github.com/golang/go/issues/15292#issuecomment-22198...
Here is the comment you linked:
> Ian's proposal demands too much. We cannot possibly develop all the features at-once, it will exist in an unfinished state for many months.
> In the meantime, the unfinished project cannot be called official Go language until done because that will risk fragmenting the ecosystem.
> So the question is how to plan this.
> Also a huge part of the project would be developing the reference corpus. developing the actual generic collections, algorithms and other things in such a way we all agree on that they are idiomatic, while using the new go 2.0 features
The author of this comment is not a member of the core team, as far as I know, and the comment doesn't suggest the proposal was rejected.
And it's symmetric with function parameters. If you think multiple return values is a kludge, then for the same reasons so would multiple parameters. After all, you can always curry multiple parameters, or shoe-horn multiple parameters into a compound objet like a struct.
Now whether it makes sense to leverage multiple return values as a substitute for specialized error types, or for exceptions, is a different question. But if you don't see the value in multiple return values than I think it's hard to assess things objectively.
I program a lot in Lua and love multiple return values. And in Lua the standard practice is to leverage multiple return values with exceptions. E.g.
local x, y = assert(bar())
where on success bar is expected to return a first value that evaluates to true in a boolean context. If so then assert just returns that first value and any subsequent values. If not then it throws the second value as an exception, which usually is expected to be an error description string or an object that can be stringified, but assert doesn't care about the type.Note that assert() isn't a specialized function. You can implement it in pure Lua. Multiple parameters and multiple return values are generalized as lists (similar to Perl) which also can be used when, e.g., initializing a table.
function foo() return "one", "two", "three" end
for i,s in ipairs({ foo() }) do print(i, s) end
prints 1 one
2 two
3 three
Of course these things can be abused and they're not strictly _necessary_. But they're powerful abstractions that can prove very useful, especially in the context of functional programming styles.Go seems simple on the outside but is actually very elegant and sophisticated internally. Multiple return values, lexical closures that automatically capture by reference and which can escape their original scope without any fuss, lightweight stackful coroutines (stackful meaing no decoration required for recursively invoking yielding functions) that automatically adjust their stack size. You usually only see these things in interpreted and dynamically typed languages, if at all, because they can be very difficult to implement efficiently with an optimizing compiler. Similar concepts in C#, C++, Rust are not nearly as powerful yet still require syntactic kludges (like decoration) to help drive the kludgy implementations. That Go implements these things seamlessly, while compiling straight to native code, is quite a testament to the Go team.
Most of the attention these days is given to stronger typing systems. And that's fine. Go doesn't win too many points on that front, although its duck typing has unique advantages, even over traits. But it's not reasonable to overlook all the other things that Go does extremely well and almost without equal for an optimizing, AOT-compiled, strongly typed language.
All of these things come with trade offs. Typing systems are hardly the be all, end all of language design. They're usually not even the most interesting part, nor the part most relevant to productivity. What's unique about Rust, for example, isn't its typing system, but its borrow checker. And the borrow checker drives directly and indirectly much about their choices with other aspects of the language. Like I said, multiple return values are difficult to implement underneath the hood, and in a language like Rust might require some really odd declaration syntax and have other unforeseen consequences. Thus you see them reconceptualizing multiple return values in terms of a kind of polymorphic compound type. That's not intrinsically better--if anything it can be seen as a kludge--but in any event it's driven by other needs than pure abstract functionality and should be not only criticized that way, but appreciated that way.
s = append(s, f())
m[k] = f()
c <- f()
f().DoStuff()
h(f(), g())
work with single-valued functions but not multi-valued functions. The feature is not well integrated with the rest of the language.Because of constructors, I don't think they would have eschewed exceptions. Constructors don't return any value (in both C++ and Java ABI terms, they are implemented as void functions!) anyways, so having the "I don't know how to do what your arguments told me to do" be indicated via an exception being thrown is an intentional model rather then using return codes.
These things are typically done by exceptions and monads; the "zig" language has some interesting syntax around this kind of thing as well.
But just multiple return values isn't enough for me - too much "if error return error" noise for my taste.
This is such a big deal, I don't understand how it's defensible. Code is read dozens of times more often than it's written. When code looks like:
if val1, err := thingImActuallyTryingToDo1(); err != nil {
return nil, fmt.Errorf("thing failed: %v", err)
}
if val2, err := thingImActuallyTryingToDo2(val1); err != nil {
return nil, fmt.Errorf("thing failed differntly: %v", err)
}
if val3, err := thingImActuallyTryingToDo3(val3); err != nil {
return nil, fmt.Errorf("thing failed again: %v", err)
}
it's impossible to tell at a glance how the function is actually working. You have to parse out 90% garbage just to get to the details of what's really happening.Worse is that it's never this "clean" and repeatable in practice. It becomes a bunch of subtly-different variants on that theme (append if the function wasn't an error, or do something different if it errored, etc.) that the actual logic of your function is obscured in a maze of error-handling.
Code doesn't become easier to read/understand by just tucking away 90% of it (the hard/interesting parts at that, mind you).
If 90% of your code really is unique, specific action to error conditions, then by all means, this topic isn't for you.
But if 90% of your code is "stop processing in this function, log the error, and pass it up the stack", then why not abstract that away and reduce the noise? (i.e. something like exceptions or monads or arrows or ...)
After a while, you just learn to ignore the `if err != nil` and get on with your life. I mean, sure, it would still be nicer to have an abbreviation, but it's not like Go code is 90% `if err != nil` (as critics always try to frame), and when I read Go code, I'm most definitely not spending most (or any) of my time stumbling over them.
And most of your code winds up doing error checks because if any call in the stack under a particular function produces an error, it usually winds up having to bubble the entire way up the stack.
I understand that. I was pointing out this is less noise than try-catch-finally (assuming the programmer is comparing Go to C++/Java/C#). I don't deny some functional langs have more elegant handling in this respect.
If you want some data, "if err != nil return err" contains the four most frequently used words in Go code. See https://anvaka.github.io/common-words/#?lang=go . For comparion, try/catch are only 34th and 35th for Java, and even lower for C# and C++.
Every call that can fail, which isn't quite every function call.
DRY principle seems not applicable here.
But that would require either more special-case magic (always bad), or generics (not in 1.x, supposedly).
It would be great to have something like a conditional return to get rid of the endless `if err != nil`, or, while we're at it, from endless `.then` that futures-based programming ends up with:
foo_result, err = foo(...)
on_failure(err, "foo failed")
bar_result, err = bar(foo_result, ...)
on_failure(err, "bar failed though foo did not")
return bar_result + 1See how mere opinions work? Now what?
Me pointing out it's a simple language is not a matter of opinion; it's an intentional design decision of Go. Generics will complicate the language more than the Go developers are willing to tolerate.
At the time of writing, there's 110 comments on this post and 50 are replies to prodigal_erik's comment on not having generics. HALF the comments on this whole thread are discussing something that's already been discussed for years with no end in sight.
For the record, I really don't care if we have generics or not. I just want a higher signal-to-noise ratio for topics about Go on Hacker News. This is a low-signal topic. You may as well argue about systemd.
Doesn't that point to an actual demand for the feature, and a constant pain point for the language? Why else would it come up in all discussions? It's not about all discussions about Go spin off to random topics / features talk -- it's usually Generics. Similarly, I've not seen people discuss anything else than common pain points (or perceived pain points) in a language. E.g. with Python it will be about the GIL or the 2 to 3 transition. With JS the "fatigue" and/or the crazy coercion rules. With C++ how complicated it is or the slow template system. And so on.
Your argument is about ease of use, but people rooting for Generics also argue that they make lots of programming cases easier. Are there many programmers in 2017 really wishing that e.g. C# or Java rather didn't have generics?
And what the original feature set as designed isn't very relevant, as languages can and do evolve. Similarly for the opinions of the main developers.
No! My argument is more meta; that this whole discussion is an unproductive waste of time and I can't tell anymore if people are just trolling or looking to start a flame war by bringing up generics in a Go thread. It's noise and it's making Hacker News a worse place.
This has been discussed to death already.
Just like Go error handling : looking on github you see that Go mostly makes it easy and less visible if people totally ignore errors.
It is, of course, possible to create libraries that don't follow the idiom.
Yeah. Thing is though, there are these libraries who access pointers (especially in Go where lots of stuff are implicit pointers, like maps and interfaces). Or, may God strike them down where they stand, people who use division. Or allocate. Or cast. Or use interface{}. All such code has implicit (non-visible) panics in them.
What I mean to say is: in practice, Go code is not fundamentally different from Java/C++ code : everything can throw. Everything. Yes even that thing that you're thinking of right now that couldn't possibly ever think about throwing. There's just people who admit this and people who don't.
That the Go authors come up with idioms that are so incredibly shortsighted is emblematic of everything else about the language.
This is how Go works. They claim they are perfection, and know better than anyone.
For instance, compare:
https://blog.golang.org/go15gc
with
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd...
(just to put up something other than the generics discussion. But everything about go suffers from both massive deficiencies and an extremely arrogant development defending deficiencies ...)
Then for `err == nil` we need some type of syntactic sugar to simplify this. I don't know what it would look like, but so many copies of that simple if statement get written that it is a bit ridiculous.
1) Java is not suitable at all the task at hand. I am just trying to solve something far more suitable for different type of language.
2) Java is working fine for the purpose and it leaves me with time to think how about Java fix its longstanding issues.
Ideally Java should fix that stuff but I come to conclusion 2) is possible only when Java works fine for me and fixing those issues is either impossible or very low priority. I think same will apply to Go or any other language.
There is no data demonstrating that statement. I don't think you are talking for the majority of people using Go. Choice is good. Generics in the language wouldn't change anything for you as you wouldn't be forced to use them.
I don't understand people who hate generics. They are just types and they are here to help the developer write compile time type safe code. interface {} everywhere isn't compile time type safe.
The biggest argument against generics, is that it would increase compilation times. Well, the compiler would be still faster than the developer copying and pasting code manually, or rewriting the same algorithm for each type variation again and again. Isn't programming about automating stuff?
For those who complain generics are too complicated to understand, I don't know what kind of developer you are frankly if you can't understand something as basic as generics yet pretend like you can work with a language that has pointers,channels and threads and mutexes... What do you think is more complicated? generics or concurrency?
While the argument itself is pointless (it's unlikely one can retrofit generics in a backward compatible way with Go), there is no need to answer with these bad faith arguments. It really sound stupid.
For Go choice is bad. Go is going so strong because any Go programmer can read any Go program and library after a couple days with the language. Can you say the same for any language that has generics?
The selling point of Go for most people is rather: hey, a language with GC (easy to manage memory) AND nice parallel/concurrency story AND static builds AND faster than my dynamic language I use.
I don't think "it lacks generics" comes into play much as any kind of benefit.
Re the selling point: You're underestimating the importance of a conceptually simple language. If you put a new developer on a project in any other language that he has never used before, he will need at least a week to get up to speed. With Go, I had colleagues sending me pull requests with real, useful feature additions to my Go programs within 1-2 days of them installing the compiler.
Sure, but maybe you're overestimating the complexity of Generics (on the programmers, not the implementation of the compiler side)?
What makes you think those same programmers could not grok them? They could grok CSP (channels etc), implicit interfaces, pointers, reflection, unsafe pointer work, and closures, but suddenly Generics are too much? Seems rather arbitrary -- especially since the language already has generic functions.
It's also the matter of initial familiarity (productive in a few days) with eventual expressiveness and power (how much more productive in a few months and more)?
Because your argument seems to me like saying Go is Nano: one can start editing immediately.
Sure, but Vim/Emacs are worth the investment in learning them, even if one feels lost for a little while when starting with them.
Neither is there data for % of all programmers will move to Go if Generics are available.
> Choice is good.
It is even better by having more programing languages with preferred feature set.
> I don't understand people who hate generics.
Sure, just like you don't understand that, it is entirely possible some people who can write useful and robust production grade applications without generics to ask what is the big deal about with generics or lack of it.
> For those who complain generics are too complicated to understand, I don't know what kind of developer you are frankly if you can't understand something as basic as generics yet pretend like you can work with a language that has pointers,channels and threads and mutexes... What do you think is more complicated? generics or concurrency?
There is no need to decide on behalf of programmers on what concepts they should find difficult and what not. For example some people using Rust find borrow checker really difficult to grasp some find it an elegant and obvious way to track objects.
It's not about moving to Go, but about leaving Go. You can be pretty sure that there will be power users leaving the language unless it addresses many issues that it has. In fact, there are such people even here on HN already. It could, of course, end up being a language for non power users with the libraries of corresponding quality for awhile. And I feel like the quality issue is already here too. But ultimately not being an attractive choice for experienced programmers cannot be beneficial for the language in the long run.
I never said that. It is you who used that kind of argument like there was any truth to it. There is none.
> Sure, just like you don't understand that(...)
I never said that. You keep on making stuff up.
> It is even better by having more programing languages with preferred feature set.
I don't think adding more programming languages to a project makes that project more maintainable and readable, it doesn't make sense. You're not going to convince me that juggling with multiple programming languages on a daily basis is "even better". It isn't.
> There is no need to decide on behalf of programmers on what concepts they should find difficult and what not. For example some people using Rust find borrow checker really difficult to grasp some find it an elegant and obvious way to track objects.
There is no need to decide that a feature you wouldn't even use yourself is bad either. Nobody would force you to use generics.
Yes and no. In a couple years, a Go developer gets hired at a shop and realizes on the first day "oh snap, these people have a years old codebase that uses generics everywhere and I'm stuck with it." Is he being forced to use them? Can he leave if he cares more about generics than his paycheck? Sure. Can you withold the combo to your safe from the home invader if you care more about what's inside it than your daughter he's pointing a gun at? Sure.
I don't think it's incomprehensible that GP prefers that a language remain simple and opinionated for the sake of what one might call "cross-developer" code.
Someone up the thread a ways mentioned 1980s C++, object oriented C++ and functional C++ as examples of how disjoint different styles of a single language can be when its building blocks allow it to be sufficiently flexible/agnostic. Another example that comes to mind is Lisp, where its ability to create macros and abstractions mean a developer can, if he chooses, essentially write his own language per project (as far as its legibility to newcomers is concerned).
C++and Lisp are both powerful, of course. But power isn't everything. Jira is much more customizable and flexible than Trello, but Trello is clearly doing quite well with the small subset of Jira features that a large chunk of the market is satisfied with.
Knuth fell into making this mistake in the 1993 Computer Literacy Bookshops interview, in which he responded thus on the topic of C:
DK: I think C has a lot of features that are very important. The way C handles pointers, for example, was a brilliant innovation; it solved a lot of problems that we had before in data structuring and made the programs look good afterwards. C isn't the perfect language, no language is, but I think it has a lot of virtues, and you can avoid the parts you don't like.
Yes, you can avoid the parts you don't like, if you're like Knuth, working on one program by yourself.
You're right, and the FAQ explicitly says that the designers aren't happy with the choice either.
If there would be a one time compile penalty for generics, it may work. The issue is that if large code-bases (Go's reason de'etre) will now take an hour to compile instead of five minutes, it's just dead in the water.
Also, it looks like they want to make Go a language where all code looks the same. So you won't find team A writing C++ like in 1980, team B writing C++ in OO mode, team C writing C++ in FP mode, etc.
It's so fanatical, that the spacing is standardized.
This is just FUD. Monomorphization takes a lot of time, but no one prevents the developer to keep the dynamic dispatch by default and just do the type-checking, this is not expensive. And for people who need more performances you add a compiler flag to perform monomorphization.
If generics are to be added, they can't hurt performance and they can't hurt compile times noticeably. The tradeoff needed is too big to be worth it. If this is such a big problem to you, there are many languages out there that embrace the complexity and the tradeoffs. Scala or Kotlin might be good choices.
Giving the user the ability to balance his needs for compile-time or his needs for performance is not «complexity for no reason» imho. There is no complexity from the user's perspective, it's just "optimise" vs don't optimise and compile quickly, like what you have with C compilers. It's really different from other kind of performance tweaks, let say the choice between two garbage collectors with different trade-offs. Anyway I'm aware of the dogmatic stance of the Go team on the «simplicity» mantra …
> If generics are to be added, they can't hurt performance and they can't hurt compile times noticeably.
As I said, pure type-check generics with dynamic dispatch under the hood wouldn't hurt compile time and they wouldn't hurt performances because dynamic dispatch is exactly what Go already does with interfaces.
> If this is such a big problem to you, there are many languages out there that embrace the complexity and the tradeoffs. Scala or Kotlin might be good choices.
Not everybody is responsible for the technical environment he is working in. And I'm not sure that telling «if you're unhappy then GTFO» somebody who says he has a problem with the language is a really good approach.
Type safety mostly appeals to developers with a low time preference. Getting a bunch of compiler errors is the opposite of immediate gratification. In a language like python, you can start running your program instantly and push dealing with bugs as far back as possible. You might spend more time, since debugging is harder and slower in the long run than making the compiler catch mistakes up front, but you get to procrastinate.
Which is often never. Part of the reason why I avoid using anything written in Python.
But between C#, Java, C++ and tons of others, it seems people do need "second" and "third" such languages...
And it's not as if generics is off the table, personally I think they will arrive eventually, here's what Go lead Russ Cox said in January this year:
I really, really doubt that this supposedly-huge crowd of developers exists who are ready-anytime-now to jump in Go and are being held back by things like you mentioned.
Because even if Go had all those things, I'm sure you would scoff at its lack of Monads, inability to easily create DSL's, presence of Garbage Collector, and no compile-time turing-complete templates ala C++. Right? Right?