Pre-generics, Go has been a very limited language. On the downside it’s verbose and error-prone, on the upside it’s very uniform. I think generics will make it less verbose and error-prone, but also less uniform - stylistic differences from programmer to programmer will be greater, similar to other high level languages.
Except that this kind of uniformity inherently tends to disappear in larger projects, to be replaced by increased complexity. Java was also designed to be a "simple" language, one trivially understandable by hordes of low-skilled code monkeys, and yet look at the added layers of complexity in many Java enterprise frameworks. More full-featured languages, when properly designed (especially, avoiding the feeping creaturitis of something like Scala), make it easier not harder to manage inherent complexity at a larger and larger scale of development. But Go is now basically stuck with its constrained feature set, and Go developers cannot easily avail themselves of these tools. This is why so many devs criticize this particular design goal; it's pretty much incoherent.
I've worked on really neat big Scala codebases and really terrible big Go codebases – a determined developer can make a mess regardless of language, and for some cases it's actually easier to end up in that place with Go than with a more expressive language with a more comprehensive stdlib. Go isn't well-equipped for a number of things, and if you have to do those a lot, generated code and workarounds like the pseudo-sets people build from maps really become liabilities.
From what I've seen as far as complex codebases are concerned, I'd say Go can be very readable for small-ish programs that lend themselves well to Go's opinionated standard library and way of doing things, but I'd definitely want to use something more expressive at scale or for anything that doesn't fit into Go's limitations well.
What I find interesting here is that Scala has the reputation of being a complex language, while its syntax and grammar are among the simplest and most straightforward there is: https://images.app.goo.gl/K1NTGUP4G9T6wfY48
I believe that this is what "scalable" means in the eyes of its creator: it offers very powerful abstractions to be adequate for mundane tasks (e.g. file-level scripting) up to complex systems and problem spaces.
Yes, as a consequence, people have created libraries and codebases with Scala that are inadequately complex and abstract for the problem at hand, but that's not something one should blame on the language but on the project/company culture. Same is true for any language: C++ templates can be abused in ludicrous ways, some of Java's design patterns make you wish inheritance would be forbidden, python's module imports potentially overwriting globals and other footguns.
No language can be dumbed-down so much that the resulting codebase becomes immune to some amount of "abstraction management", not go, not even python. Once this is understood and accepted, it becomes obvious that it's not worthy to castrate a language on the basis of "simplicity", because eventually this complexity will be warranted at a point in the future or for some aspect of the project.
I think the go designers did the right thing here, I don't think this will bring dramatic changes in how the language is used, and will actually help with "managing abstraction" where it's due.
Here's more on that: https://www.lihaoyi.com/post/StrategicScalaStylePrincipleofL...
You can't dumb down a language and expect the complexity of the problem space to go away (developers will have to cope with it one way or another), and you can't expect the programming language alone to be the sole judge of what's an acceptable level of abstraction (or architects would have been made redundant by now).
- List
- Array
- Dictionary
- Set (map with empty value)
- Stack (easily emulated with list)
- Queue (channel)
Other than the ones mentioned before, the only ones I've ever used were a priority queue and sorted map (AVL-tree), and even those were rare. Other than these I used others that were so application-specific, that it made no sense to be generic. Based on this there's no strong case for why generics have to be in the language. Not hating, but I'm not convinced either way.
This takes up unnecessary space—use map[T]struct{} instead, which also explains to anyone reading the code that the value isn't used.
if myset[thing] { ... }
With a map[T]struct(), you end up with:
if _, ok := myset[thing]; ok { ... }
Sometimes, the saving in space is TOTALLY worth the extra verbosity. Sometimes, the slight clarity from "non-existent keys return the zero value" wins.
Using a "set's" value in an if statement doesn't make sense either, compared to
if _, exists := something[item]; exists { ... }
which is explicit regarding what's actually going on.As Rob said, "Clear is better than clever." https://www.youtube.com/watch?v=PAAkCSZUG1c&t=875s
I also don't want to be hunting down whoever creates a map[T]bool and doesn't leave a comment explaining it's a set ;)
Hopefully we won't have to worry about it for much longer regardless, since I imagine we'll be using proper sets (with methods) some point in the near future.
But, that's one of those "requires extensive documentation" (and ideally, wrapping in a custom type and provide methods for checking and manipulating the set(s)).
In either way, even with that type of data type underlying a set, I would (probably) provide a functional API to interact with it.
m["x"] = foo()
for k := range {
// if foo returned false, this is buggy
}for k, v := range m { if v { } }
But it also allows you to use "if m[key] { ... }". It is literally a trade-off for what convenience you want. And in both cases, you should (probably) wrap an abstraction around the raw map.
- Sets
- Trees/graphs
- Stacks/queues
- List-like data structures with different performance characteristics than slices
- Monadic data structures you find in functional languages, like Option (for dealing with the lack of a value), Either (one of two possible values), Future (basically either a success or an error, computed asynchronously), etc.
I’m sure we’ll see popular libraries providing all of the above, and more. Generics will make for a wider variety of programming styles in Go, which has downsides as well as upsides.
Go will likely get a bit more functional/declarative/high-level, a bit less imperative/procedural/low-level. It’s not about to become Haskell or Scala, but maybe a bit more like TypeScript or statically typed Python, a bit less like C.