The upcoming iterator design for Go 1.23
gingerbill.org
gingerbill.org
I didnt had a single line of code broken by golang version up updates in 7+ years, scanning through golang source code is always a pleasure. No hidden magic. I dont read documention either - just surfing inside of source code by clicking with CMD key.
My main KPI for Golang is the ammount of clicks required to understand internals ... very few compare to other languages.
If its stamped by guys like Russ Cox (rsc), I can sleep well too.
thats the whole point of the article. this change trashes that. now many loops are going to have hidden magic
Fast learning curve for new hires is a top golang feature for me too.
- 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!
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.
Will probably be the same for iterators.
I think especially because people were already doing iterators, just in a not-really-specified way, as the discussion[1] for this change mentions. (I mean people were kinda doing generics too with code generation but that was probably less doable for library code...)
But goroutines? They are a foundational language feature with clear syntactic demarcation and documented behaviour.
I think that in computer science "Magic" doesn't mean that the implementation is hard to understand. Magic means that the behavior is surprising, that there is misdirection involved etc
Sometimes it seems like people throw around the word "magic" like it's some morally dubious shortcut used by programmers who lack the discipline to resist its temptations. I'd argue that making a good language that doesn't require using "magic" is actually _hard_; it needs to differentiate from existing languages enough not to be redundant but still try to fit into the non-uniform expectations of users.
Rather than saying that a language "has magic" or "doesn't have magic", I wish it was more common for people to directly state that a language works like they'd expect or point out the areas where it doesn't (which can be separated from a value judgment of whether something is "good" or not). If anything, I think that saying a language works like you expect is _more_ of a compliment than saying "it doesn't have magic" because it properly conveys the sense that our expectations are fairly narrow in comparison to the entire design space and require skill to identify rather than something obvious that people would only avoid by choice.
This has been changed since Go 1.22. Go 1.22 introduced magical hidden code: https://go101.org/blog/2024-03-01-for-loop-semantic-changes-...
The reason they changed the behaviour is because the previous behaviour was surprising and thus "magical". The new behaviour is more consistent with what you think about what is a scope of a variable.
The fact that a compiler has to add some code to instantiate a variable is not magical. Otherwise all what compilers do would be magical
But the change on "for;;" loops creates more surprises than before and gets rare and tiny benefit. It introduced magic hidden code. Please read that article for what is the magic hidden code and the surprising cases.
But the internal consistency between the for;; and for range allows you never doubt about the rule again once you first learned it. Yes, there is the risk that you'll get it wrong once (if you first learn the language or if you are used to the old rules) but if the language authors had only fixed for range, then a lot more people would get for;; wrong just because they would be never sure which way it would be.
So the only "solution" would be to never fix "for range" or disallow for :=;; ?
In any case I think that if you really care about the old semantics, it's not a bad idea to making it obvious to the reader that you intend to directly or indirectly take a reference to the iteration variable and that it effectively survives the loop body with:
var i int
for i = 0; i < 4; i++ {The change of "for-range" loops is good is because no implicit code is introduced.
And the change of "for;;" loops is bad is because implicit code is introduced. Implicitness often causes surprises. The implicit line
pa_last, pb_last, pc_last = &a, &b, &c
in the new semantics is absolutely an evil.And the seriousness of this problem is that, if you upgrade your go version in go.mod files, the behavior of the your old code might change. And the change might be not always easily found in time.
> but if the language authors had only fixed for range, then a lot more people would get for;; wrong just because they would be never sure which way it would be.
This is just a problem in theory, it is never proven (and now no chance to prove it).
> So the only "solution" would be to never fix "for range" or disallow for :=;; ?
I think it is just fine to only make changes on "for-range" loops, just as C# have done. The assumed problem of "for;;" loops is never proven, or just proven to be tiny. IMHO, it is just rsc's personal willing to make the change. This might be the worst decision made in Go history.
The problem of the change is that, if you upgrade your go version in go.mod files, the behavior of the your old code might change. And the change might be not easily found in time.
I indeed haven't found any surprising cases caused by the change of "for-range".
I take that to mean that they're searchers, not researchers. I.e., "Googlers" here means they use Google to search for answers, not that they're Google employees.
[1]: https://www.youtube.com/watch?v=iTrP_EmGNmw (quote is at 20:30)
It not "hardly the language philosophy", it's the language philosophy stripped of the ego-fluffing layer of marketing to language end-users.
> In short, Go is not just a programming language. Of course it is a programming language, that's its definition, but its purpose was to help provide a better way to develop high-quality software, at least compared to our environment 14 plus years ago.
> And that's still what it's about today. Go is a project to make building production software easier and more productive.
I think there is an idea that Go is "software engineering for the masses", and that's why the word "easier" is used, and not "simpler". "easier" is fine when you're starting out and you don't know what you don't know. As times goes on, you start understanding more because you have more experience, and so you may yearn for something simpler, with less abstractions, more control. Go isn't the best language for this, but it's also far from the worst.
But the author of the article does point at an important reason to do it like this:
> Allow for clean-up with defer
When the range syntax would be syntactic sugar for an iterator created as a local variable which handles the state somewhat like this:
iter := Iterator{
Next: func() bool {...},
Current: func() T {...},
}
for iter.Next() {
loopvar := iter.Current()
...
}
then there's no way (in the current language) to call its clean-up function on panic, whereas it comes naturally in the current proposal. Since these are functions we rarely write, I don't think the syntax will be a problem in practice. Perhaps they could add Yield[T] type to the standard lib to reduce some of the func-iness.The only gripe people may have is when it turns out to be much less efficient than a regular range loop.
I don’t see an issue having the for range syntax that works magically for go std lib containers extended to custom containers, especially since go std lib omits so many valuable containers
Will never happen. Best you can hope for is new functions
https://github.com/golang/go/issues/61902
https://github.com/golang/go/issues/61900
https://github.com/golang/go/issues/61899
https://github.com/golang/go/issues/61898
Yes there are and a lot of the work is already done.
But they already do, slices are iterables, and the input is bounded. Plus you have lots of other benefits like indexability. What need would it solve?
> scanner.Scan, sql.Query
Right, these are not suitable for slices because they’re unbounded. But still, what’s the use case? You still have roughly the same amount of code, no? Even the same noisy if err != nil checks. Can you provide a snippet that highlights the benefits?
> Can you provide more motivation for range over functions?
> If the results can be generated one at a time, then a representation that allows iterating over them scales better than returning an entire slice. We do not have a standard signature for functions that represent this iteration. Adding support for functions in range would both define a standard signature and provide a real benefit that would encourage its use.
> There are also functions we were reluctant to provide in slices form that probably deserve to be added in iterator form. For example, there should be a strings.Lines(text) that iterates over the lines in a text.
So to cut it short, iterators, as Go implements them, should be useful at least for the following cases:
- Iterating over custom collections (e.g. trees, concurrent maps, queues) that are not provided by the language (and "range for" doesn't have custom code for inside the compiler).
- Iterating over a collection in a different way, for instance filtering out some elements or iterating over the collection backwards (as in the giving example).
- Reification - if abstract iterators can be passed to functions (instead of actual slices), you can write functions that operate on a theoretically infinite stream.
- Chaining iterators. If iterators can be reified, you can also chain them together. This is somewhat cumbersome with the syntax Go 1.23 will offer, but in many languages you can write code like this:
let incompatibleTransactions = user.Accounts
.Filter(account -> account.Balance <= 0)
.FlatMap(account -> account.PendingTransactions())
.Filter(tx -> !tx.AllowCredit())
Once you know what each function does, it is clear that the code goes through all the accounts that have a zero or negative balance and returns all the pending transactions that do not allow credit from these accounts.The equivalent imperative code is harder to figure out at first glance
incompatibleTransactions := []Transaction{}
for _, account := range user.Accounts {
if account.Balance < 0 {
for tx := range account.Transactions {
if !tx.AllowCredit() {
incompatibleTransactions = append(incompatibleTransactions, tx)
}
}
}
}
If the accounts and transactions collections are not built-in the iteration code would even look worse. This code is harder to read since it conflates the What (what we want to achieve with the code) with the How (how to implement this with a for loop). Obviously, this is what imperative programming is all about, and Go is indeed becoming less imperative.[1] http://wiki.c2.com/?BlubParadox [2] https://sac.edu/AcademicProgs/Business/ComputerScience/Pages...
This is why I'm despairing so much over Go's popularity after using C#/F# and Rust which have rich and idiomatic iterator APIs. Go implementation is plain ugly, flawed and, most of all, is going to be slow unless you write it in the most verbose way due to Go's compiler weakness.
There are languages that move us forward, Go sets us back by 20 years.
Take these things a little less personally. Live and let live. People think differently from you, and that's okay.
Instead, people shoehorn it into data processing and databases, the domain at which .NET is incomparably more adept due to performance primitives it offers, if we still talk high-level-ish languages with automatic memory management. Of course if there is no such requirement, there are many (like Zig, Rust and C++) systems programming languages that are better in every possible way at solving the task. And yet, we get Go and startups struggling to scale. But all the interesting problems in which Go is attempted to be applied to, where the solution, if successful, is despite Go not because of it, would have benefited from C#/F# which offer much better control and performance assurances, without having to fight the tool.
I have meticulously benchmarked Go in previous roles and also used Go as a load generator and with smart allocation strategies the language is quite fast. I think you're letting your emotions cloud your judgement but that's nothing new in PL discourse. There's no better way to bring out those that identify with/against programming languages than PL discourse.
See, I would not call Zig unexpressive. I've written a number of iteration structs in Zig, because it has true optionals this is easy, using them is just:
var iter = thing.iterateSomeWay();
while (iter.next()) |item| {
// do stuff
}
It's a low level language, yes, but an expressive one given that design constraint. Go just makes this stuff harder, because nil is just zero wearing a costume, an antipattern it inherited from C. In Zig every type can be nullable, and the compiler takes care of representing the null case distinctly from the load-bearing one.Also by not doing enough to make UNIX culture startups have a different opinion of Microsoft, instead of still doing M$ jokes.
The same kind of startups that won't have any qualms adopting Swift, or dealing with its current poor state on GNU/Linux.
By way of analogy, consider that almost any language is missing lots of features present in some other languages. For example, Go does not have inheritance. If I do not miss that feature while using it, that does not necessarily mean that I'm a Blub victim.
As for the chaining, yes I do miss filter and map but is again totally doable with vanilla slices (they just haven’t added those methods to the slices package).
tldr: The gold standard is if your iterator syntax / syntaxes can support these two types of iteration:
1. Interleaving two sequences
2. In-order tree traversal
I'd argue the primary magic in large-scale languages should mainly be in those two places: inversion of control and concurrency.
Especially concurrency is hard to get right, so it's probably better to make it as clear as possible as to what's happening?
Btw, would garbage collection count as 'magic' in your book?
I don't mind the pace that Go has been moving at when it comes to new features. It still feels very conservative while still evolving.
I still grumble every time I have to implement one. Something about the interface just bothers me. Feels sloppy.
Then again I can only understand key Haskell concepts when they’re explained using any of several other languages (then they’re typically very easy to understand) so maybe I’m not the best judge of what is or is not Haskell-like.
Look at Rust iterators which are just simple and readable ‘next() -> Option<Item>’ that can be targeted with ‘for item in iter’ and chained iterator expressions or C#’s yield return or IEnumerator<T> (which support cleanup on panic too) which works with foreach and the same kind of chaining. Both are faster, easier to read and write too.
Instead of pulling the same quote from Rob Pike over and over and over again, people should watch/read is recent talk about Go titled "What We Got Right, What We Got Wrong": https://commandcenter.blogspot.com/2024/01/what-we-got-right.... A few excerpts:
> Given the title of this talk, many people might expect I'm going to be analyzing good and bad things in the language. Of course I'll do some of that, but much more besides, for several reasons.
> But the real reason I'm going to talk about more than the language is that that's not what the whole project was about. Our original goal was not to create a new programming language, it was to create a better way to write software. We had issues with the languages we were using—everyone does, whatever the language—but the fundamental problems we had were not central to the features of those languages, but rather to the process that had been created for using them to build software at Google.
> The creation of a new language provided a new path to explore other ideas, but it was only an enabler, not the real point. If it didn't take 45 minutes to build the binary I was working on at the time, Go would not have happened, but those 45 minutes were not because the compiler was slow, because it wasn't, or because the language it was written in was bad, because it wasn't. The slowness arose from other factors.
> And those factors were what we wanted to address: The complexities of building modern server software: controlling dependencies, programming with large teams with changing personnel, ease of maintainability, efficient testing, effective use of multicore CPUs and networking, and so on.
> In short, Go is not just a programming language. Of course it is a programming language, that's its definition, but its purpose was to help provide a better way to develop high-quality software, at least compared to our environment 14 plus years ago.
> And that's still what it's about today. Go is a project to make building production software easier and more productive.
I will repeat it because I think it is very important:
> And that's still what it's about today. Go is a project to make building production software easier and more productive.
Another quote about Go being more than the language:
> A few weeks back, when starting to prepare this talk, I had a title but little else. To get me going, I asked people on Mastodon for input. A fair few responded, and I noticed a trend in the replies: people thought the things we got wrong were all in the language, but those we got right were in the larger story, the stuff around the language like gofmt and deployment and testing. I find that encouraging, actually. What we were trying to do seems to have had an effect.
And finally:
> Perhaps the most interesting consequence of these matters is that Go code looks and works the same regardless of who's writing it, is largely free of factions using different subsets of the language, and is guaranteed to continue to compile and run as time goes on. That may be a first for a major programming language.
> We definitely got that right.
So now that we know better what Go is about, we can try to see why the iterators are added. From https://github.com/golang/go/issues/61405#issuecomment-16388.... I'll quote some parts:
> Can you provide more motivation for range over functions?
> The most recent motivation is the addition of generics, which we expect will lead to custom containers such as ordered maps, and it would be good for those custom containers to work well with range loops.
> Another equally good motivation is to provide a better answer for the many functions in the standard library that collect a sequence of results and return the whole thing as a slice. If the results can be generated one at a time, then a representation that allows iterating over them scales better than returning an entire slice. We do not have a standard signature for functions that represent this iteration. Adding support for functions in range would both define a standard signature and provide a real benefit that would encourage its use.
> There are also functions we were reluctant to provide in slices form that probably deserve to be added in iterator form. For example, there should be a strings.Lines(text) that iterates over the lines in a text.
To me this provides enough justification for adding the iterators. But that's not the important part. The important part is listening to what people say about Go, not just "Go the language", but Go as a whole. And listening to what people said recently, not what they said a decade ago. This is how you genuinely engage with other people, I think.
> Go is a project to make building production software easier and more productive.
> Go code looks and works the same regardless of who's writing it, is largely free of factions using different subsets of the language
But I don't understand how these citations align with iterators in Go:
- Iterator functions are non-trivial to write and read because they return a function, which accepts a function, which accepts iterable items.
- The 'for ... range' loops with iterators are non-trivial to debug, since they __implicitly__ call iterator function and then __implicitly__ pass loop body converted to __implixit__ anonymous function, to the function returned by iterator function.
- Iterators do not replace existing functions - they add __new__ functions because of backwards compatibility. This complicates writing, reading and maintaining Go code, since there are multiple ways to do the same thing.
- Iterators encourage adding crappy iterator functions to the existing code, making it more complicated and harder to maintain.
Either give us magic or make it supremely readable. This accomplishes neither - it's a barely legible mess of questionable value.
To be clear, I'm not against features. But I am against anything that makes code this ugly by default.
But hey, maybe I’m just being a curmudgeon and it’ll be great.
As the article mentions, there was nothing stopping you from doing this sort of thing (and worse; channels as “iterators”, anyone?) in older versions of Go. But it would be silly to do, and you’d get (rightly) scolded for doing it.
Now I guess that kind of thing is encouraged. :)
I don't understand the aversion to this, if you don't like it don't use it, but this cleans up the multiple different loop patterns and collapses them into a for range syntax, now you have 1 way to loop instead of multiple ways.
The actual implementation of this, making an iterator, is more of a fix for the people developing custom container libraries, which the majority of the go community probably doesn't do. You can go on being a productive go dev and literally never learn this syntax, it won't change your use.
I dont mean that as a statement against the author, I mean it as a statement to the language designers. people are gonna start writing code like this, or worse, and Go is gonna end up like many other unreadable languages. shame.
Also, whenever I encounter multiple returns it feels very cluttered and I have to stop and think what the heck is going on.
And finally the the capitalization of variable does horrors to my understanding of the code that I’m reading, I know what it is for, but as I’m reading code, if a function being used is an imported from somewhere else is not a meaningful information, and just confuses me.
I bet there is more, but these are some of the examples I’m consciously aware of
There is something about Go that feels "off" for me as well.
I started picking it up but couldn't get past some of the syntax.
As someone who has seen the cost of C++ templates metaprogramming wizardry gone wild I understand part of this reaction. I reckon we would have a real talent shortage bigger by orders of magnitude if we required familiarity with Alexandrescus' Modern C++ Programming and Herb Sutter's Exceptional C++ to onboard new programmers in a project.
Yeah, definitelly there's such a thing in programming as "too clever".
But should we so radically into the opposite direction? Should we sacrifice any attempt at expressiveness into the altar of simplicity? Aren't we going too far into the opposite direction?
The following code is composed from a small set of simple operations,
transformed = []
for (i = 0; i < len(list); i = i + 1)
{
el = list[i]
temp = transform(el)
transformed.append(temp)
}
but together the simple operations form a complex operation. The effect of each individual line is immediately clear, but the effect of the whole procedure is (marginally, in this toy example) obscured. However, If we extend our small set of simple operations with complex but common operations, we can express complex operations with conciceness that, overall, makes them simpler to recognize and reason about: transformed = list.map(transform)
The effect of the line now requires more background knowledge, but in return the effect of the entire procedure becomes more readily apparent.Passing around functions or using them as return values is a common pattern in programming. It's also not the kind of knowledge that needs relearning either. You learn about it once and move on. If this can't be considered required knowledge for software professionals, I don't know what can.
We have been through this reboots multiple times, Pascal vs Algol 68, C vs PL/I, Java vs C++, ....
Now compare where those "simpler" languages are 20 to 50 years later, compared with their version 1.0, and the amount of "could it been better" features if they were there in version 1.0.
Go folks, thought they are special and this wouldn't happen to them.