Hacker News new | past | comments | ask | show | jobs | submit login

> No hidden magic

thats the whole point of the article. this change trashes that. now many loops are going to have hidden magic




If iterators are hidden magic, how are the following not 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?


> for i, v := range someSlice { ...

what a ridiculous comment - every single programming language has this or similar syntax for iterating a slice.


All the languages I can think of that have iteration over a slice also have the ability for users to define iteration over custom collections, such as with generators or whatever... yet the comment I'm replying to is saying custom iterators, something every language has, is too much magic, so clearly "every language has it" isn't enough justification for something not to be magic for them.

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.


Go always had hidden magic:

- 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.


The attitude of 'generics for me, but no generics for thee' that the language designers had before they were introduced in the wider language is a particular pet peeve of mine.

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.


I will never understand why anybody gave a shit about this "not for thee" thing. I get being irritated that the language didn't have generics! I didn't care, but the complaint made sense. But the language having a small, capped number of useful generics seems to me, as a normie, as a good thing. It was always weird that people personalized this.


I think this grievance mostly comes from the core language team being reluctant to introduce generics early on[1]. The core team was never entirely hostile to generics (although some vocal parts of the Go community were), but they were quite dismissive of their usefulness. I think the arguments about "generics for me but not for thee" came to demonstrate the hypocrisy, when debates got heated and the aforementioned vocal parts of the Go community rose up with their usual Luddite themes of "we don't even need generics or any language feature cooked up by academic eggheads after the 1970s".

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).

https://research.swtch.com/generic


Thanks, I agree with most of your points.

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 Go designers were never against generics. They just didn’t want to rush a design in without careful consideration.

The real complaint should probably be “they took too long to decide”.


Not they were, they even acknowledge not having done the work properly in regards to researching existing implementations.

" 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...


That statement doesn’t disagree with my comment.

There are also plenty of comments online from Cox saying he isn’t against implementing generics, they just want time to do it properly.


"Do it properly" is an educated way in office politics to hand wave issues.

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.


Not always. And the fact that Go now has generics should be evidence enough that Cox was always open to the idea of generics even if he didn’t feel rushed into implementing it.


Cox is not the founding team, and Rob Pike has publicly expressed he is not happy with the generics decision, but it wasn't his decision to take.

"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)


Pike isn’t the final word on Go either. Hence why we have generics.

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?


Doesn't change the fact that the original authors weren't into generics, let's stop rewriting history here.

Don't worry, Go still has lots of stuff to complain about.


I think they are just addressing this statement you made earlier:

>The Go designers were never against generics

(Fwiw I think I agree with both of you in this situation)


There was nothing to "rush".

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.


That doesn’t mean that zero research is needed to define the best syntax and implementation approach for, specifically, Go.

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 theory. The non-ideal state of Java and (especially) C++ generics suggested that it might be unwise to rush into a particular set of implementation choices.


The non-ideal state of generics in Java mostly stems from having to maintain compatibility with early versions of Java that did not have generics.

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).


In practice.

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.


Every programming language is frequently a poor choice for somethings and frequently a good choice for others.

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.


Meh, I couldn't give a stuff about the personality traits of the Go designers. I like the language, which I think is both better and worse for not having followed every modern trend in PL design. I don't know the designers personally. If they are arrogant, so be it.


"Lowercase symbols are private to the package they are declared at"

This isn't magic, it's just a clever convention.


It's clever, right up until you come up against the fact that many languages don't have uppercase/lowercase (e.g. Japanese) - and now you have to add an extra hack to work around your original hack.

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 wonder if users of other alphabets really use their alphabet to code in roman based languages. I mean, even the keywords are not transcribed, right? I guess they could probably prepend a v or V to their variable names? And especially if the code is to be shared with an international community? Wondering.


I've never seen that happen. English is my third language and the only time I've seen people talk about programming in other languages was either when referring to very old esoteric prog languages, or in English speaking forums ironically enough. It happens and I've seen code written in french for example but it's considered an anti pattern almost everywhere.

I guess sometimes it makes sense for constants or context dependent variable names but again, still very rare.


I was once brought into a PowerBuilder project, during the discovery phase due to my French skills more than anything else, and naturally was cheaper than getting an external translator.

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.


That's interesting! Was it an older code base? I know France also has a few "domestic" platforms, I just didn't think about it for some reason. It's also probably a lot more common in anything that intersects with BA or that encodes "real life" rules. The other exception is comments, I also still comment my code in french whenever I need to refer to a design choice or whatever since we discuss them in french usually!

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...


Yes, going back to the PowerBuilder vs Visual Basic vs Delphi glory days.


It is a part of the language rules that you can't ever change if you don't want it. It is magic in that sense.


I always thought that lack of "magic" in a programming language means that when (as a human) you read through source code you are able to reach the end of the program. Opposite to that, in a language that has "magic" you reach a point where you don't know where execution will go next without the help of the compiler (or a heavy duty IDE that translates for you).


That's very subjective I guess, because it would also depend on your familiarity with the subject matter. I can for example easily read and understand a recursive descent parser in any language because I have implemented it multiple times and can catch a common pattern. Unless the language in question corrupts that pattern (highly unlikely unless it's an esolang :-), that definition of "magic" should be largely independent from the language itself.


It's not about understanding the language, it's about how an element in the code can mean different things. Think how in Ruby you can get monkey patched behaviour that overwrites a method name for some type, or even the init() function in Go, that someone mentioned in the sibling comments that gets executed at module load, so when you encounter state that should be a zero value, it's actually not.

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.


You also can't change the if statement, is that now magic too?


Yes, that's why I needed "in that sense". As noted in replies, I believe many naive notions of magic really depend on one's background and context.


It is not clever, especially for serialisation


A sufficiently clever convention is indistinguishable from magic.


except there's no smoke and mirrors for this character saving trick


not at all - you ever tried to export or unexport an identifier module wide? its a huge fucking pain in the ass - versus a one line change with other languages


I would love a program analyzer able to generate a list of magic behaviors:

  $ 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]


That's called disassembler and CPU architecture manual for a specific generation (because the way it is executed is magic too) :)


> - 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.

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.


GP made the point a few lines below.

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


This has in fact come up before, and you may be surprised to learn the approach they take is most reasonable.


I'm the first to argue that Go is "most reasonable" all around.

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)


Something I have found surprising and tripped over is that the commands are not the same as the language and the Go team generally does not scrutinize changes which break invocation of commands in the same way that they do the language via the Go 1 compatibility guarantee.

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.


Perhaps I wasn't clear.

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.


To be honest that seems worse. It feels overly restrictive and surprising. What sort of error do I get? Is the file ignored?

Sometimes perfect is the enemy of good.


Well, if the file got ignored that would be very surprising and a source of frustration indeed

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


Also, frustratingly, ”_unix.go" doesn't work IIRC even though "unix" is a built-in build tag, because the ending thing only works with platform names. You need to manually do "//go:build +unix" in the file.


For me init is magic not because it's implicitly called.

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!


I guess my system got immuned to such things after TS/JS coding, but if 20% of community will say it has "hidden magic" I'm not excited too.

Fast learning curve for new hires is a top golang feature for me too.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: