thats the whole point of the article. this change trashes that. now many loops are going to have hidden magic
thats the whole point of the article. this change trashes that. now many loops are going to have hidden magic
for i, v := range someSlice { ...
Shouldn't that be eschewed as magic, and instead we should write: for i := 0; i < len(someSlice); i++ { v := someSlice[i]; ...
They both de-sugar to the same thing (well, now they do, it used to re-use the same 'v' in the first one, so it used to be subtly different, but they changed the language's hidden magic).How is the channel loop not hidden magic?
for v := range channel
// magic for
for {
v, ok := <-channel
if !ok { break }
}
The iterator proposal is adding some sugar which is _less hidden_ than the current 'for range' loops, and people are complaining that's magic?what a ridiculous comment - every single programming language has this or similar syntax for iterating a slice.
Anyway, not every language has it. C has arrays, but doesn't have any iteration sugar for them, and the general attitude of gophers does usually seem to be "if C didn't have it, it's not simple, it's magic", and that Go should just be C but with a GC, goroutines, and builtin hashmaps.
- Lowercase symbols are private to the package they are declared at.
- A function called "init" will get executed implicitly on startup.
- You can have multiple functions called "init" in the same package or even in the same file.
- Files ending with "_unix.go", "_linux.go", "_windows.go" etc. will only be compiled when compiling for the specified platform. The exact list of platform is very hard to find in documentation.
- There are a handful of magical built-in function that have lowercase names.
- Some built-ins were generic long before generics were introduced to the language.
- The copy function is defined as "copy(dst, src []Type) int" (i.e. both src and dst have to be slices of the same type), but "As a special case, it also will copy bytes from a string to a slice of bytes".
There are many cases of magical behavior in classic, pre-Generic Go. Sure, if you read the documentation you can learn about these thing and you wouldn't be surprised (although good luck figuring out the order of calling init() or finding out what happened when Go introduces a new compilation target platform that happens to have the same name as the ending of one of your source files). The thing is, if you read the documentation about iterators and see that your iterating over the results of a function that returns a another function (rather than a slice, string, map or channel), you also wouldn't be surprised.
And unlike the old magic, the new magic is always transparent. You can always tell what an iterator function by looking at its source code. But you can't exactly tell what suffixes trigger platform-specific compilation without looking at the Go compiler source code, and knowing where to look.
For comparison, C++ is one of my least favourite languages, but at least they try to put the user of the language on an even footing with the designers when that's possible.
If we'll be fully honest, every language has irregularities that could be qualified as magic (and I've never claimed otherwise). For instance, Pascal has magical varargs for the builtin Write family of functions and most modern languages with generics (such as Pascal, C and Java pre-1.5) have generic arrays. Modern Java arrays are also reified and unboxed, while other generics are boxed and the type is erased in runtime. Go shouldn't be criticized for having exceptions for some language internals and other types of necessary "magic".
My personal beef is with the claim that Go is unique by not having magic, while most other language have "hidden magic". I believe that's utterly and categorically false.
My other issue is magic that is non-transparent and not well-documented (or hard to wrap your mind around), like the platform-specific source files in Go and multiple init() functions in the same package. I'm not looking forward to the time Go introduces a target platform that happens to be a common word and mysteriously breaks lots of programs...
[1] To be exact, they didn't claim that generics are bad, but that they require expensive trade-offs and I remember one of them (perhaps Russ Cox?) saying that generics are mostly useful for custom collections and this is not a big-enough use case. This rubbed many of us as a little bit arrogant, since the "trade-offs" that were quoted only applied to Java and C++, two of the most atypical implementations of generics. None of the quoted issues applied even to ML and CLU which introduced generics decades before Java and C++ (back in 1970s).
Your footnote is especially noteworthy: most of the arguments against generics I saw from the Go authors struck me as arguments against C++-style templates only. And I can see why you don't want your language to become like C++.
But the authors also gave the impression that C++-style templates are the only kinds of generics they knew about and that they could even envision.
> [...] saying that generics are mostly useful for custom collections and this is not a big-enough use case.
Yes, all the while implementing their favourite collections generically as a hard-wired language built-in.
They claimed that their void-* style 'generics' (via the empty interface) could solve most problems, but they didn't even use them for their own key-value data structure.
As an example of their earlier 'generics' being good enough, they touted how well they can represent arbitrary sorting algorithm. But, of course, they could only represent single-threaded in-place sorting algorithm that way.
The real complaint should probably be “they took too long to decide”.
Generics, by the time the first version of Go rolled in, were a well understood field with proven strategies of inference and lowering. Not by Go designers, I suppose.
But I think this response showcases a particular trend within Go the language and its community: arrogance and insufficient acknowledgement of the outside world. There's myriad languages beyond Java and C++ both of which have rather non-standard "generics" implementation (object erasure and code templating). But, adhering to this trend, it seems Go design decided not to absorb much from the prior art. I know, surely designers of a popular language should know better? That's what conventional wisdom would suggest, and yet here we are.
But hey, I should cheer on - the bolted on approach that will plague Go for the years to come might just help companies realize sooner that Go is frequently a poor choice.
As someone who’s been writing software for 30 years and in well over a dozen different programming languages, I can tell you that there’s no such thing as a language that doesn’t have any problems. And that’s without even touching on programmers personal preferences.
If James Gosling had "rushed" and copied generics Verbatim from CLU or Ada, Java would have had a very decent generic implementation. Not necessarily a perfect one, but it wouldn't suffer from the serious issues that C++ and Java suffer from (and more or less ONLY C++ and Java suffer from).
It just means it should have taken less time to decide on that.
Which comes back to my original point: Cox was never against generics despite the popular meme claiming he was. People’s real complaint should be the time it took to decide upon an official approach.
I get why some people are frustrated. But at the end of the day, most of the people who comment about it on HN aren’t people who do any development in Go to begin with. So it often just feels like people taking potshots to troll rather than an honest conversation about the merits of the article (eg in this instance, it has nothing to do with generics).
" In retrospect, we were biased too much by experience with C++ without concepts and Java generics. We would have been well-served to spend more time with CLU and C++ concepts earlier."
-- https://go.googlesource.com/proposal/+/master/design/go2draf...
There are also plenty of comments online from Cox saying he isn’t against implementing generics, they just want time to do it properly.
There are enough discussions on go-nuts on the contrary, plus it isn't as if there wasn't enough examples to choose from, between 1976 and 2009.
"Sydney Golang Meetup - Rob Pike - Go 2 Draft Specifications"
https://youtu.be/RIvL2ONhFBI?t=1018 (starts here)
https://youtu.be/RIvL2ONhFBI?t=1892 (he expresses his opinion here)
I don’t really understand why you’re arguing about this. Go has generics now, isn’t it about time you moved onto a new soapbox?
>The Go designers were never against generics
(Fwiw I think I agree with both of you in this situation)
Don't worry, Go still has lots of stuff to complain about.
$ penn-and-teller ~/projects/go
- init() is implicitly called on program start [link to explanation]
- xyz_unix.go() will only be used when compiling to unix [link]
- file.go:20:20 calls a bult-in function [link]This isn't magic, it's just a clever convention.
If as a developer reading the code, there's no way to know if the compiler injects some behaviour in or around the thing that you're looking at, that's magic.
Any time you add a clever hack that re-uses something you don't control directly (Unicode, file names, existing data formats, implicit behaviors of a system - or even explicit since you don't control who will change it later, etc), you're making a mistake that will come back to bite you eventually.
In fact, I dare to say: Any time you think you're being clever, you're not.
Most of the design mistakes of go were made under the assumption that all complexity can be reduced. There are certain kinds of complexity that can only be moved around, not reduced (e.g. init, os-specific compilation, packaging and such).
I guess sometimes it makes sense for constants or context dependent variable names but again, still very rare.
Everything except API calls was in French.
I also have seen enough code during my lifetime with comments, and occasionally code as well, in Portuguese, Spanish, Italian, German.
I think your first point is exactly why it's not as common as it used to be (imo, I don't have any stats to back this up!). It makes hiring people, or getting support a bit harder, especially if you need consultants for example. Plus the docs are very often in English anyways...
Sure, you won't find it in the Go 101 guides, but it is well documented in the "go build" CLI reference in the official Go docs site. See https://pkg.go.dev/cmd/go#hdr-Build_constraints and https://pkg.go.dev/internal/platform.
What if Go adds support for a new OS called "FoOS" and suddenly your files named "my_foos.go" stop being compiled anywhere else?
They should have forbidden using _ in file names except for an allow-list of suffixes
Even things which are technically wrong and _could_ have been done differently are still reasonable.
First and foremost because sometimes the alternatives would have meant breaking backwards compatibility (or at the very least forcing people to hassle with migrating code with "editions", which are probably better left for more impacting problems than "what if a new OS comes around")
That said, I think it's important to call out design mistakes when one sees them (as long as one engages constructively with them instead of just throwing a random "Go is magic/sucks/etc" without putting things into context, like how do other practical languages fare on all the metrics combined)
I think overall the semantic filename build constraints `_GOOS_GOARCH` as well as `_test` suffixes provide real value in that I immediately know that the file is build constraint guarded, and it aids my ability to read/browse code greatly. If that information were not encoded in the filename, but only in build tags in each file, then it would be a fairly significant hit to my productivity. I can't see any alternative that is not more complex, and I have issue finding that complexity justified.
I think there is a tradeoff here, the Go team knows it, and that in practice the tradeoff is worth it. There are many such things in Go, tradeoffs of purity and theoretical issues for the sake of practicality, and by and large they're okay.
I also like the use of the file name "pre-extension" to categorize files.
There was a way to have the cake and eat it too which is: forbid _ inside the file name other than the extensions that have a well known meaning and treating the others as reserved.
Sometimes perfect is the enemy of good.
A clear error message that explains that go source files cannot have underscores in them other than the supported suffixes (and a link to a page that documents them).
If you do that since the beginning then it's easy and painless.
The problem is: what do you do when you haven't thought of the consequences of one convention? Do you fix it later?
I'm happy with the current Go trade-off. This detail isn't worth fixing.
But it's nevertheless interesting to use as an excercise to see how it could have been done in a way that preserves all good properties and is also future proof
It's magic because unlike any other function it can appear in multiple files in the same package. And the order of execution depends on the names of the source files!
Fast learning curve for new hires is a top golang feature for me too.