6 ms·
This was also an issue in Javascript (e.g. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for https://developer.mozilla.org/en-US/
by noelwelsh 3y ago
This was also an issue in Javascript (e.g. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...)
It's somewhat amusing to see Go rediscover old ideas in programming language theory, given the stance against PLT that the Go developers took in the early years of the language.
- philosopher1234 3y agoWhat stance against PLT are you referring to?
- pseudonom- 3y agoProbably quotes like: "It must be familiar, roughly C-like. Programmers working at Google are early in their careers and are most familiar with procedural languages, particularly from the C family. The need to get programmers productive quickly in a new language means that the language cannot be too radical." And not including sum types despite having a sum-type-shaped hole in the language (`if err != nil`). And some of the discussion about "why no generics" seemed kind of divorced from existing PL knowledge on the topic.
- tensor 3y agoThese were all intentional tradeoffs though, not any ignorance of theory. Also, it's pretty rich for someone to be complaining about Go while referencing Javascript of all languages. Javascript's design flaws are legendary. And I mean no disrespect to the creators of Javascript, they had to deal with some crazy last minute change requests to the language.
- kaba0 3y agoWith all the crazy warts that JS has, it is at least a lisp-like very dynamic language if you squint (a lot) at it. Its greatest fault is probably leaving out integers (I can’t even fathom why they decided on that, floats can’t represent properly ints). Go is just simply badly designed, relying on hard-coded functionality a lot.
- Cthulhu_ 3y agoWhich is better, hard-coded functionality or magic? I don't believe it's badly designed, since every design decision is deliberate; bad design would be accidental.
- za3faran 3y agoThis is a false dichotomy. golang is weak that things that should have been in the standard library end up being hardcoded (like maps).
- TwentyPosts 3y agoEhhh, I see absolutely no evidence that the Go developers were particularly aware of theory. It really feels more like they just were used to thinking in terms of C, and built a language which is kind of like C. Go also has some really weird stuff in it, such as named return values. Frankly, the lack of sum types hurts the most. The language would just be a lot better with a unifying Result type in the library. And don't give me any of that "oh, they tried to keep the language simple!" stuff. Intuitively, sum types are laughably simple. Everyone understands "It's one of these possible values, so you need to check which one it is and then handle that situation." They are more simple than enums on a conceptual level! Sum types are just not how C-programmers think about the world.
- ianlancetaylor 3y agoAs it happens, we considered sum types quite seriously in the early days. In the end we decided that they were too similar to interface types, and that it would not help the language to have two concepts that were very similar but not quite the same. https://groups.google.com/g/golang-nuts/c/-94Fmnz9L6k/m/4BUxp-JqZFUJ https://groups.google.com/g/golang-nuts/c/-94Fmnz9L6k/m/4BUx... There is a lot of discussion of sum types in Go at https://go.dev/issue/19412 https://go.dev/issue/19412.
- TwentyPosts 3y agoWhen I did research on this topic ages ago I read both of these links, and I'm very familiar with the arguments. I also vehemently disagree with them, and I think that the way code is factually written in practice is on my side: People who propose sum types commonly refer to the Option<T> or Result<S,E> types in Rust. These are types which are almost exclusively used as return types. Interface types are the opposite. They're used as input types, and almost never to distinguish between a concrete, finite list of alternatives. They are used to describe a contract, to ensure that an input type fulfills a minimal set of requirements, while keeping the API open enough that other people can define their own instances. The fact that Go in fact does not use interface types for its error handling is a pretty good argument in favor of that, I'd say. The thing is, at this point it doesn't matter, sadly. Adding sum-types to the language now would be unsatisfying. You would really need proper integration as the primary tool for error handling in the standard library (and the ecosystem), and that's unlikely to happen, even less likely than a Go 2.0. EDIT: Just to make it clear, I think not wanting to add sum types to the language is understandable at this point. The real shame is that they were not in the language from the beginning.
- akira2501 3y agoProgramming languages are an exercise in compromise, not pure application of theory. Well, except maybe Haskell and it's ilk, but this should be your expectation of most languages and generally not a surprise.
- FridgeSeal 3y agoI think OP’s point is that given the go devs casual disregard for every development in PL theory and design over the last 30 years, it’s amusing to watch them rediscover half the issues from scratch.
- skybrian 3y agoThis meme is commonly repeated on HN, but I think it's inaccurate or at least greatly exaggerated. There's nothing casual about Go's approach to language design, and I haven't seen any evidence that they're unaware of what other languages do. I also haven't seen much criticism of languages other than C++ or Java.
- bobbylarrybobby 3y agoIf they're aware of how other languages either handle these issues or suffer the consequences of not handling them, then it sure is odd that they consciously decided to introduce the same issues into their own language, only to fix them down the road.
- skybrian 3y agoGiven Go’s success, it seems like fixing certain footguns much later actually worked out pretty well for them? That doesn’t necessarily mean it was right, but it was perhaps not as big a deal as some people assume.
- FridgeSeal 3y agoSum/Product types. Generics/type-parameters. Bizarre handling of nil in places. Error handling that’s like some deliberately crippled version of a Result<T,E>. The absolutely unhinged decision about zero-value-defaults for types. I’m sure other people can think of some more, but that’s the ones I can think of off the top of my head. Non PL theory but related: incorrect implementation of monotonic clocks, and then refusing to fix it because “just use google smear time bro”.
- anderskaseorg 3y agoMDN goes into great detail, but the important point is that JavaScript fixed this with let and const. > a = []; for (i = 0; i < 3; i++) a.push(() => i); a.map(f => f()) [ 3, 3, 3 ] > a = []; for (let i = 0; i < 3; i++) a.push(() => i); a.map(f => f()) [ 0, 1, 2 ] > a = []; for (i of "abc") a.push(() => i); a.map(f => f()) [ 'c', 'c', 'c' ] > a = []; for (const i of "abc") a.push(() => i); a.map(f => f()) [ 'a', 'b', 'c' ]
- zeroimpl 3y agoIn contrast, Java would have a compile error so long as the variable was not declared final.
- ben0x539 3y agoBut isn't it completely wild that the `for (let i =`... version works like that? What does the loop 'desugar' into if written as a while-loop?
- anderskaseorg 3y agoBefore let/const, scopes could only be introduced at function level, so @babel/plugin-transform-block-scoping transpiles it using an extra function: var _loop = function (i) { a.push(() => i); }; for (var i = 0; i < 3; i++) { _loop(i); } The key is that the scoping happens for each iteration, not around the entire loop. That detail is nonobvious, given how many other languages have gotten it wrong, but I wouldn’t say it’s wild. (If you’re curious how Babel deals with the more complicated cases of break/continue, labelled break/continue, and return, try it out at https://babeljs.io/repl https://babeljs.io/repl.)
- ben0x539 3y agoRight, the wild thing for me is when you mutate `i` in the loop body. So at the same time `i` is scoped to the iteration so that you can capture it, but also mutating it affects the loop-scoped `i` that is incremented between iterations and checked for the termination condition. The iteration-scoped `i` is assigned back to the loop-scoped `i` at the end of the loop body. So if you have a closure close over `i` in the loop body and mutate it, whether that mutation affects the actual loop variable depends on whether the closure is called during the iteration it was created in or during a later iteration. Kinda spooky, but sure, less of a footgun than the original behavior.
- biomcgary 3y agoIs this loop variable problem really a theory issue (if so, does it have a label)? Or, primarily a practical one? Is there a database of known and historical programming language problems that are nicely tagged with "theory problem", "frequent foot-gun", etc? Could an LLM coupled with a constraint solver create the perfect language (for particular domains, e.g., performance)? Or, just use Rust ;-)?
- noelwelsh 3y agoIn contrast to the view of many, programming language theory is very closely intertwined with programming practice. It both drives practice and reacts to practice. In this particular case I imagine the issue was uncovered some time in the 1970s, which is when lexical scoping came to the fore in Scheme (in the US) and ML (in Europe). It's a fairly natural problem to run into if you're either thinking deeply about the interaction between closures (which capture environments) and loop constructs, or if you're programming in a language that supports these features.
- stouset 3y agoThe entire story of go seems to be learning through repeating the same mistakes as other languages, one at a time. Nil being another big one. Even more impressively they doubled down on this mistake with typed nils. Even if you explicitly do a comparison with nil you can still shoot yourself in the foot because it was a different nil than the one you compared against.
- TwentyPosts 3y agoWait what? Do you have an example or more information on this?
- Thaxll 3y agoop is confused with nil interface.
- gwd 3y agoBasic example: func foo() *bar { // ... if something_wrong { return nil; } } var x interface{} x = bar() if x != nil { // Dereference x } This will crash if `foo()` returns nil, because it's checking if `x == interface{}(nil)`, which is false. What you wanted to check was whether `x == *bar{nil}` or one of the other nil types that implements the interface; which must be done with `reflect.ValueOf(x).IsNil()`. https://github.com/golang/go/issues/30865 https://github.com/golang/go/issues/30865
- kiitos 3y agoThis is a great example of code that compiles, but would never pass code review at any decent organization. Specifically, you'd never assign a concrete return value like *bar to an interface{} and expect `x != nil` to behave like this code would imply.
- stouset 3y agoYes, it’s a contrived example. But it’s not like this is some obscure thing that no go programmer has ever run into in practice. It’s something I’d wager almost everyone has encountered if they’ve used it longer than a year. https://dave.cheney.net/2017/08/09/typed-nils-in-go-2 https://dave.cheney.net/2017/08/09/typed-nils-in-go-2 If Dave Cheney says it hits every go programmer at least once, it caused hours of consternation for his coworkers, and it even has its own entry in the language FAQ, I don’t know what else to tell you.
- doctor_eval 3y agoIt’s also amusing to see someone talk about JavaScript and PLT in the same sentence, given the practical origins of JS.
- noelwelsh 3y agoIt's not making any claims about the quality of design in JS here. I'm literally not talking about them in the same sentence for this reason. I'm, instead, merely noting that it had the same issue. Looks like it was fixed in 2014: https://nullprogram.com/blog/2014/06/06/ https://nullprogram.com/blog/2014/06/06/ C# also had the same issue, as noted elsewhere in the comments.
- deleted 3y ago[deleted]
- doctor_eval 3y agoIt simply seems disingenuous to talk about how this was an issue in JS and then go on to say that Go devs have a "stance against PLT". JS is not renowned for being an exemplar of PLT, so why would the Go developers use it as a reference point for their own design? JavaScript can't even take the address of a variable - which is the underlying problem here. In any case - I seem to remember that they discussed the rationale for the original decision in the release notes for go 1.21, along with additional context. For whatever it's worth, I don't see any evidence that Go is specifically antagonistic to programming language theory at all - the existence of first-class constructs like channels and closures suggests otherwise. There are always costs and tradeoffs involved in adopting certain theoretical paradigms, and PLT is subject to fashion as much as any other endeavour. Go focusses on simplicity, and when talking about simplicity I really like this quote from Dijkstra: "Simplicity requires hard work to be obtained and education for its appreciation, and complexity sells much better.” [0] I think Go works hard to be simple, and sometimes that comes across as being simplistic. Indeed, I was sceptical of Go when I set out to learn it, but having spent enough time with it to consider myself a professional Go developer, I also find that enjoy coding more than I have for many years, `err != nil` notwithstanding. [0] https://www.cs.utexas.edu/users/EWD/transcriptions/EWD10xx/EWD1041.html https://www.cs.utexas.edu/users/EWD/transcriptions/EWD10xx/E...
- deleted 3y ago[deleted]