I can agree with most of the article, but for some it looks like we have not been using the same language ecosystem at all.
Go really feels opinionated around the wrong things, in order to claim "simplicity" as a feature:
- No generics just mean you're going to be handing interface{} all the way in your stack, it makes things more complex and less readable for no reason (and less safe)
- error handling which is essentially string-based, in 2017? Give me a type system, please
- gofmt is a good idea (just like clang-format or yapf), and having it is great, but the maintainers specifically refuse to add simple features, saying "running it in an automated process is not supported", despite all github projects already having it in their automated CI suite
- go test is good, but in the end it is found lacking
- dep, well, if you don't mind dumping the code for all your deps in the source tree, it's probably ok, but it solves a deficiency that the language should not even have (see next point)
- stability: the language itself is fine on that, but the ecosystem really isn't. The fact that they don't even have the package management quality of python (which I find awful already) is baffling. I don't even know how they could think "yeah, github-importing is a good idea, let's do it". Go is eight years old tomorrow and you still have to rely on third-party tooling to do proper imports that don't burn your house.
>- No generics just mean you're going to be handing interface{} all the way in your stack, it makes things more complex and less readable for no reason (and less safe)
Depends on what you do. I've written a good chunk of go code and interface{} is the rare exception rather than the rule, usually employed where a user might supply arbitrary types (ie a unmarshaljson like function)
But if you do a website or webapp, 99% of your code is not using interface{} in it's methods.
>- error handling which is essentially string-based, in 2017? Give me a type system, please
You can put anything behind an error:
type IntErr uint64
func (i IntErr) Error() string {
return strvonc.Atoi(i)
}
The string is only present when you either use the stdlib errors or you output an error.
Additionally, nothing in Go forces you to actually use the error interface (sans stdlib).
You can invent your own Error interface that uses ints. (Won't work with most external libraries but nothing is stopping you)
Panic and Recover can to my knowledge both handle things that aren't errors; recover returns a interface{} that you check for value and type. You can put anything in there.
>stability: the language itself is fine on that, but the ecosystem really isn't.
A lot of projects focus on stability. If the API is breaking a lot they usually use Gopherpit or gopkg.in
to ensure compatibility in the future.
In my experience, breaking the API is not something well used libraries do lightly (libraries with no or little use do break it sometimes since they lack the usage to refine it)
> But if you do a website or webapp, 99% of your code is not using interface{} in it's methods.
That's not really true. If you want an SQL database to back your website, then you'll deal with interface{} with Go's sql package. If you want to gzip your output, then you'll use interface{} with your writer.
To be fair, I think interface{} gets an almost unfair amount of hate. While it is fscking annoying when passing around core types (I hate having to use switch clauses just to inspect the type of an interface{}!!!) I do love how interface{} is used for complex structures. In fact there are a few areas of Go's standard library which I wish used interface{} more in that respect (eg Go's `file` struct should be an interface{} so I can create custom methods for os.Stdin/out/err)
No, they are interfaces, but they are not interface{}s, the interface that is met by having no methods and is therefore met by everything. Nobody is complaining about the use of interfaces that declare a useful set of methods, and are meant to be used when the set of methods is all the target code cares about. They may complain about some of the edges around them (no support for contra- or covariance), but that's not the complaint that is being discussed here.
The complaint is specifically around interface{}, which is a type that means nothing, and so everything fits into it. It means that if you are a function that is receiving an interface{}, you know nothing about the argument to start with, and will have to either pass it along to something else, or examine it via type assertions or in the worst case, the reflect library.
As a multi-year Go programmer, I agree that A: the "true" interface{} that is the meaningless type appears less often than people think, and that if you program is shot through with it, you are either working in a domain where Go is not suitable (complicated math processing, IMHO, for instance), or you are programming some other language in Go, and need to learn how to use Go properly, but also, B: it does happen that things that would be well-typed in other languages will have interface{} show up, which means you're going to sacrifice runtime safety and some performance, though the exact impact depends on what you are doing.
(I say the "true" interface{} because there's also some places where interface{} shows up, but it doesn't hurt you. For instance, the standard JSON library accepts an interface{} to say what data structure to parse the JSON into, and the code that implements that is somewhat complicated, but for the most part, that interface{} doesn't bother you, the Go user. In theory you can encounter run time errors if you pass in something that doesn't work, but in practice, if you pass in the same type every time, which is the natural case that is going to result if you just program normally, it's not a problem. In general, `interface{}` showing up in the incoming parameters of a library is much less concerning than an interface{} showing up in the return parameters for a library. In the former case, even though the language does not enforce type safety, you can enforce type safety by how you use the method; in the latter, you are stuck with an interface{} in your code with all that implies.)
"io.Reader is literally an interface{} though. I think what you're referring to is the other packages that define their own Reader's, ie using structs with methods, those are referred to as interfaces despite not literally being an interface{}."
That appears to be a definition unique to you that I have never seen from anyone else before, including the Go designers. io.Reader is an interface, no braces. Its full expansion would be interface { Read(p []byte) (n int, err error) }, and it is incorrect to substitute that for interface{} as that is a very different thing. Structs that implement the interface are, well, structs that implement the interface. interface{} is specifically reserved for discussion about the empty interface.
For instance, do a browser find for "interface" in https://golang.org/doc/effective_go.html ; you will find the documentation frequently referring to "interfaces" and that interface{} is reserved for the empty interface.
For the final proof of this, note how confusing it is for you to be reading the Go criticism as being about the use of interfaces-in-general in Go. Why would that provoke such a reaction? The answer is that people aren't talking about the feature, they are specifically referring to the number of places interface{} appears, the "Any" type, the "I don't know what's in there at all" type. It's not the use of interfaces in general, it's the way that suddenly one goes from a decent enough statically-typed language to a not-all-that-great dynamically-typed language when interface{} appears. (I disagree with the HN gestalt about the severity of this problem, but I understand it and still agree it's a non-zero problem.)
This is correct. I think part of what you're saying, in fewer words, is that there are interface method signatures, and there are interface expressions, which are ultimately different.
With this statement, you've shown you have close to 0 Go experience.
* interace{} - a place-holder for a value of "any type". It means "a value that satisfies the empty interface", i.e. an interface with no methods. All types implement it. To do anything with the actual data it encapsulates, you have to do a type assertion to unbox the value. The type assertion will panic or return an error if the type assertion is incorrect. This is something like C's void , or Java's Object.
io.Reader, io.Writer - these are interface types that specify a set of methods that must be implemented by another type in order to satisfy that interface. These are akin to C#/Java's interfaces, but are less restrictive in the sense that any type in Go can implement them, not just "classes". E.g., http.HandlerFunc - a function type that implements the http.Handler interface.
Yeah, it is unfortunate. My ranting was born from frustration that HN used to be a refuge from the usual pointless egotistical jibes as seen on other forums and social platforms. But over the years the quality of counterarguments has slowly been lowering to the level where it's now commonplace for people to make judgemental remarks about a persons ability based solely on a solitary semantical error (I obviously get the distinction between the two - I just wasn't aware they were described differently in literature).
I have amassed a large body of real world projects under my belt over the last 3 decades and have used Go for 6 of those years. I've written some pretty interesting stuff in Go like file systems compiled against FUSE and compilers. Yet I by no means consider myself an expert in anything - least of all Go - as to do so would imply I'm not open to learning something new (even basic things I somehow missed during my initial studies). Despite that I still find it highly offensive and unnecessary for someone who knows nothing about me to say "you have close to 0 Go experience." when I've clearly demonstrated and described various Go-isms prior to his or her remark.
Edit: weirdly I've got more down votes for this comment than my last one. I guess some people think rudeness is acceptable. Sad times.
> That's not really true. If you want an SQL database to back your website, then you'll deal with interface{} with Go's sql package.
True. But for most real world scenarios I only ever use interfaces at the boundaries between APIs and they quickly get asserted into a type after being passed.
Conceptually they're a bit like passing marshalled data between APIs in that sense.
> Conceptually they're a bit like passing marshalled data between APIs in that sense.
That's IMHO a great way to think about when it's Good™ to use them. Yes, there are times when you just have to use the empty interface type but it's really not as bad as everyone makes it out to be.
I love Go because it is explicit and because explicit types make everything easier in the long run. I get tired of seeing Go written like Python/Ruby/$otherDynamicLang with overuse of interface{} and/or reflect.
> But if you do a website or webapp, 99% of your code is not using interface{} in it's methods.
How do you deal with the lack of sets and trees? I believe those two data structures are frequently almost indispensable - even in the most boring mostly-CRUD web projects. And it's either type assertions on {}interfaces or go-generate based specialized implementations. Both feel odd to me. (Am I missing something?)
For Sets, you can get away with the language slices and maps, which work sufficiently well.
I haven't found a need for trees in most of my projects and where I did I implemented it when necessary.
You can however, if you need to, separate the code and data of a tree by using a data adapter. The tree only stores a unique ID for an object which you can receive using the adapter, which would be a simple map or slice.
But as mentioned, I very very rarely needed them and not yet in any CRUD web project.
Context: I've been programming in Go full-time for the last 2 years and come from a Python and C# background.
> No Generics
The lack of generics was something I was hung up on, too, but TBH I haven't found myself reaching for them for a while now.
> Error handling
This is not a problem for me, either. I can easily handle different `error` types and if I want to provide more context I pull in github.com/pkg/errors
> gofmt
gofmt is great
> go test
go test has been totally sufficient for me and I was quickly writing and running tests. The testing package itself is builtin and writing tests is dead simple.
> dep / dependency stability
Dependency management has been a huge PITA with Go and is my largest complaint. Personally, I've used everything go GB vendor, godeps, glide, and now dep. dep has been Just Working for me and is decent enough working on a team. As for dumping the dependency code in your source tree-- it lives in a folder called ./vendor so it's easy enough to not look at it.
> The lack of generics was something I was hung up on, too, but TBH I haven't found myself reaching for them for a while now.
I mean, if you lose an arm you probably stop trying to pick stuff up with your missing hand after a while too, doesn't mean it's not better to have two arms.
Are generics as useful as an arm? I think that's a slight exaggeration. I'm sure they're useful in a lot of situations but there's a lot of programs you can write where their absence is neither here nor there.
> The fact that they don't even have the package management quality of python (which I find awful already) is baffling. I don't even know how they could think "yeah, github-importing is a good idea, let's do it". Go is eight years old tomorrow and you still have to rely on third-party tooling to do proper imports that don't burn your house.
Python packages are installed via a separate tool so I'm not sure what you're getting at here.
Including github in the import path is a good idea. It makes it so you always know where the source code for any package is. This consistent approach empowers sites like godoc.org. I don't know how many times I've tried fruitlessly to find documentation for libraries in other languages, but in Go the developer who created the library gets it for free. I wish more languages did this.
Do you remember the left-pad fiasco in npm? Out of the box you'll have similar problems in Go, but at least you'll get them when you work locally, not when you spin up a new node. And to fix the problems you end up having to do the same thing regardless. Either you vendor your dependencies or you setup some sort of caching middle-man proxy.
Go's dependency headaches aren't the fault of the language or the ecosystem - Go actually has a really well crafted module system, specifically designed to fix major performance issues in languages like C++ and Scala. Rather they're a reaction to the problems latent in the "easy" solutions and simply switching is just trading one set of problems for another.
> It makes it so you always know where the source code for any package is.
Where is a useful question to answer, and nice to have consistent across an ecosystem. But What is also a very important question to answer, and tying What to be mutable and dependent on When is... highly unfortunate.
The opinions expressed in Go are solely those of Google about the productivity of Google programmers. Nothing else is driving design. It is by Google employees for Google employees. Making it widely available and open source is a strategy for extracting value in the form of bug reports and patches and Google friendly upgrades from a world wide community for the price of a few conference talks and some sticker swag.
That Go solves some problems for programmers outside Google is a side effect of being a general purpose programming language. It is not a primary business goal except in so far as it raises Google's bottom line, creates a hiring pipeline, and keeps its employee moral off the floor.
I think if you're replacing "Google" with "those Bell Labs guys", you're closer to the truth. A lot of the design decisions are quite familiar if you look at the past work of e.g. Rob Pike.
It's not necessarily all about peak efficiency for specific enterprise applications.
I agree completely. Go is opinionated about the wrong things, in the wrong way. Which in the end make it a worse language than it could have been.
One thing you did not mention is very varied quality of the standard library. I’m still baffled about ”standard date” crazyness. There are huge warts in the sql interface etc
My collegues used to joke golang is a great solution to google’s problems (for others, not as good as it arguably could have been to put it mildly)
- Go's opinion about generics has always been that it should have them, but finding the right expertise to implement them in a non-ridiculous way has been a struggle. They are marked for inclusion in coming versions (2.0).
- error is an interface. If you are returning strings most of the time, you're probably doing it wrong.
- Wasn't gofmt originally executed during compile time? I'm not sure why it changed, but it suggests that such automation fell apart in practice.
> Go's opinion about generics has always been that it should have them, but finding the right expertise to implement them in a non-ridiculous way has been a struggle. They are marked for inclusion in coming versions (2.0).
The fact that people can claim that C#, Java, Scala, Kotlin, Haskell, Rust, Swift have "ridiculous" implementations of generics sounds like a very bad excuse.
The Go team has, until recently, been quite open about not being confident enough in their own abilities to implement it well in Go. Maybe to the point of absurdity, but they were explicitly warned by designers of other popular languages (specifically some of those on your list, I believe) to not be cavalier in how they add generics.
That doesn't mean nobody on earth can implement generics well. I'm sure if someone from the C#, Java, Scala, etc. teams, with the necessary expertise, wanted to step in and contribute generics for Go, it would have been a welcome addition. At very least for the community who would have jumped all over a fork with generics. However, I do not see that code anywhere. You cannot force people to do things they do not willingly want to do.
Now that the Go team has had enough time to put for the effort to learn more about generics and the research related to them, they feel more confident about being able to add them in a somewhat reasonable way and have officially announced that they will be added in the near-ish future.
> gofmt is a good idea (just like clang-format or yapf), and having it is great, but the maintainers specifically refuse to add simple features, saying "running it in an automated process is not supported", despite all github projects already having it in their automated CI suite
A Golang noob here. Can you (or anyone else) expand more on this? Are there any links that I can read more about this?
Actually, the mistake is mine, I meant to write golint (but too late to edit). I have no issue with gofmt. As for golint, https://github.com/golang/lint/pull/100 is the kind of thing I'm thinking about.
The Go team aren't the most respectful, but I think Dsymonds is uncharacteristically rude. Most of my experiences with the rest of the Go team have ranged from "sorta terse, but I'll give the benefit of the doubt" to very positive.
I've been writing Go for quite a while, and probably used everything it's got to offer as a language. Go is opinionated, yes, however, for some crazy combination of reasons, it's amazing at getting out of your way for the most part.
Using interface{} instead of generics is a bad idea, you lose type safety. It's messy, but code generation is your other alternative, sort of like your own templating system. I've used go's built in templating code to generate type safe Go code from things like SQL table schemas. I'd highly recommend that route over naked interface{}
Error handling is string or type based, that's up to you. I declare specific error types for my functions, like FooError or BarError, the caller can then switch on err.(type) and respond appropriately. Alternately, you can define concrete error objects and return those, and make comparisons like "if err == FooError".
I'm with you that dependency management is terrible, vendoring is a poor workaround.
> - stability: the language itself is fine on that, but the ecosystem really isn't. The fact that they don't even have the package management quality of python (which I find awful already) is baffling. I don't even know how they could think "yeah, github-importing is a good idea, let's do it". Go is eight years old tomorrow and you still have to rely on third-party tooling to do proper imports that don't burn your house.
Google operates on a mono-repo basis, as I understand it (from speaking to ex-googlers.) All libraries etc. etc. all under one big repo. If you think about how that would appear to end tooling consuming it, the approach taken with imports makes sense. It's lousy for the rest of us outside of the google mono-repo ecosystem, but it does make sense.
I am new to go and am especially curious about what you find lacking on go test. Could you elaborate on that point? I've found it nice to work with so far and wonder what kinds of issues others have had/what I can watch out for. Thanks!
Why? I'm not saying go is a bad language at all, it has a lot of good points, as outlined in the source of this discussion. It is however not a perfect language and as such, it never hurts to criticize it, if only in the hope that it will improve in the future. I personally find the mental model required for go quite abrasive, but that's only me, and I won't hold it as valid criticism.
The alternatives vary according to the issue you want to tackle, so I would be hard pressed to recommend a particular one. And go does fill some vacant spots in language space.
That's the problem, I see the following often suggested instead of Go:
Java - maybe if you use Spring Boot you can get around some of the boilerplate, but there is a ton of baggage in the ecosystem.
Clojure - This is really popular where I work but LISP + Java Ecosystem turn a lot of people off. Also, some people really want static typing (maybe Spec is good enough?)
Node/TypeScript - terrible concurrency, good for throwing something together that works rather quickly, good for cutting corners. Terrible memory, cpu profiling. As a professional node developer in the day job I could go on and on, but at least the type system is better than Go (too bad the concurrency story is so abysmal)
Elixir - not popular, but at least it's on BEAM VM and has lots of libraries. Not statically typed (yes you can use dialyzer). Never done anything with it, but apparently deployment is a pain.
I personally write JavaScript mostly because its ubiquity. It's good enough, until it's not.
Go really feels opinionated around the wrong things, in order to claim "simplicity" as a feature:
- No generics just mean you're going to be handing interface{} all the way in your stack, it makes things more complex and less readable for no reason (and less safe)
- error handling which is essentially string-based, in 2017? Give me a type system, please
- gofmt is a good idea (just like clang-format or yapf), and having it is great, but the maintainers specifically refuse to add simple features, saying "running it in an automated process is not supported", despite all github projects already having it in their automated CI suite
- go test is good, but in the end it is found lacking
- dep, well, if you don't mind dumping the code for all your deps in the source tree, it's probably ok, but it solves a deficiency that the language should not even have (see next point)
- stability: the language itself is fine on that, but the ecosystem really isn't. The fact that they don't even have the package management quality of python (which I find awful already) is baffling. I don't even know how they could think "yeah, github-importing is a good idea, let's do it". Go is eight years old tomorrow and you still have to rely on third-party tooling to do proper imports that don't burn your house.