Comment by troupo

4 hours ago

Go is rediscovering Guy Steele's Growing a Language from first principles, at a much slower pace: https://youtu.be/_ahvzDzKdB0?is=qZdfiT3792XyHxSJ

It's sad that such basic stuff took this long. If we're lucky we might even see a `Map` function in our lifetimes.

  • Go's whole philosophy was to keep the language simple. It's a shame they're abandoning that now and becoming the next C++/Zig/Rust/Java

  • The beauty of go is YAGNI. Language design makes it much harder for people so do stupid and cute things.

    During my design process if I start realizing that I’m missing maps, More expressive Types, Or more complex polymorphism. I ask myself if I really need those things.

    If I really do. I move off of go.

    That’s the beauty of the language. Go does not need more complicated language features because it’s can handle the majority of trivial software work without unnecessary complexity.

    The language does not need to solve complicated problems.

    • Ok, and what if you realize you want these things a million lines in? Do you still move off of Go?

      Generic programming isn’t some fancy research language feature like dependent types. It’s just a bare minimum feature in any modern typed language.

      It’s perplexing that after C# and Java both notably shipped without generics initially then added them later that they decided to ship Go without generics.. only to end up adding them later.

      6 replies →

  • This is also why people eventually leave languages. Satisfy everyone’s wishes for long enough and you get lots of different styles of doing things and … «wow look at that much simpler language over there; let’s try that instead»

  • Map function leads to poor code in Go. Function literals are verbose and inlining is far less agressive. It simply isn't the way of the language. Even python shuns map and filter in favor of comprehensions. A for loop is more readable than the lambda soup.

    • IMO it depends on the language… to me, map/collect/etc are useful in languages that have the concept of immutable data, because you can initialize an array with a single call chain, and be sure that nothing after that can modify it.

      What I’ve seen in the “for loop” approach that I can’t stand, are things like (pseudo code)

        var a = []
        for x in coll1 {
          a.push(foo(x))
        }
      
        do_stuff_with(a)
      
        // … further down the function
      
        for y in coll2 {
          a.push(bar(y))
        }
      
        do_other_stuff_with(a)
      

      Reading code like that, is the first call to do_stuff_with(a) a bug, because it’s not fully built from both coll1 and coll2 yet? Or is the second call to do_other_stuff_with(a) a bug because a now contains more stuff than the developer probably thought? Can I safely move both loops next to each other, or does that break something subtle? If I need to pass a to a new function, where can I safely do this? Before or after I add from coll2? (In my actual times seeing this, a is really a map of cached key/values or something, and it’s kinda ok that the contents were different each time it was used, but subtle bugs emerged…)

      IMO the sane way to do it is to just not incrementally mutate things like that, and stick with giving things a single place where they’re defined and initialized. Go doesn’t really help you here because there’s no such thing as immutable data. So just adding Map/collect or whatever doesn’t really buy you much.

      1 reply →

    • map functions are very useful for iterating over collections. Most functions iterating over collections are useful, because a lot of the data you're commonly dealing with is collections. And most collections are generic.

      > Function literals are verbose and inlining is far less agressive.

      > Even python shuns map and filter in favor of comprehensions.

      That's the problem of the language design. And Python isn't the best language to turn to for language design

      > A for loop is more readable than the lambda soup.

      A for loop shoving modified items into a temp variable with append() that is then returned is less readable than a map function transforming data. Too bad Go decided to turn lambdas into unreadable soup.

Sounds like a win to me. I'd assume that many PL decisions come with with costs of either implementation or readability, and Go's conservative evolution takes that into account. Disclaimer: not a PL designer, just a codemonkey.

It's not a perfect language, and the process has not been without its pain points, but work like this makes the language more powerful and I feel like I get my money's worth when I use it.

Yes, I'm a Go fanboy but I'm not interested in language wars (e.g., yes Rust is more performant and correct but sometimes "good enough" is good enough).

  • On the other hand, design decisions made under one set of constraints can become problematic when you add new features that don't play as well with earlier design decisions.

    For a concrete example, the fact you cannot define custom methods for external types in Go (a pre-1.0 design decision) means that you cannot write proper compile-time generic code to deal with some generic wrapping type (dumb example -- getting a sum of the perimeters of a generic set of shapes in an externally-defined collection type) -- you are forced to work around it with runtime type-switching. There are all sorts of arguments you can make about simplicity but this is an objective shortcoming of Go that is directly caused by generics being added to Go long post 1.0.

    My take on this is that despite selfishly wanting more from Go for many years myself, adding more stuff to Go at this late stage is just slowly chipping away at Go's uniqueness with features that make a large number of people using them unhappy. (For example, while they make some APIs nicer, iterators turn a basic logic bug that is impossible with for loops -- forgetting to stop iterating when the loop has a "break" -- into a runtime panic. And they really suck to compose.)

    • __For a concrete example, the fact you cannot define custom methods for external types in Go (a pre-1.0 design decision) means that you cannot write proper compile-time generic code to deal with some generic wrapping type (dumb example -- getting a sum of the perimeters of a generic set of shapes in an externally-defined collection type) -- you are forced to work around it with runtime type-switching. There are all sorts of arguments you can make about simplicity but this is an objective shortcoming of Go that is directly caused by generics being added to Go long post 1.0.__

      I'm a bit intrigued by this: how would that work with compatibility between libraries, separate compilation and what not? My first instinct is that it would not be a good idea but I might be missing something..?

    • > (dumb example -- getting a sum of the perimeters of a generic set of shapes in an externally-defined collection type)

      There's a much more common example: dealing with external APIs that return JSON. Their response is trivially wrapped in Req<T>. Pre-generics Go would force you to write tedious duplicated boilerplate code for each request type, or just "cast to void*" with interface{} and hope for the best.

      > adding more stuff to Go at this late stage is just slowly chipping away at Go's uniqueness

      Uniqueness should not be the goal of a language.

    • Fair enough. Primeagen did a video recently renouncing his love of Go over the concerns you've raised.

      That said, I'm assuming much of this "wobbly" functionality will live in libraries and will not be common in day to day coding (much as I've seen with generics so far). It's all optional after all.

      I use Go because it's simple, capable, and been my prime language for over a decade and that skill enables me to be valuable enough for a decently paying job.

      If I were younger with more energy and time on my hands I'd likely be a rustacean but I think I can ride out my career on this. YMMV.

      2 replies →

  • "Everything is a trade-off" assumes you're on the Pareto frontier.

    Go is... very much not on the Pareto frontier of programming language design.

    • > "Everything is a trade-off" assumes you're on the Pareto frontier.

      100%. it pisses me off so much that i have to hear this idea thrown around constantly.

    • Somewhat. The trade off is it can be made more complex at the cost of its simplicity. You can argue that some of those tradeoffs don't in fact have to be made because it's not on the Pareto frontier, eg generics, but we'd have to have a specific discussion about specific language features instead of vague generic discission about the language.

    • I find language wars tiresome at this point. There was no claim that it was a frontier language, or even that it was a great language. My point was that the Go authors do try to consider the impact of enhancements to the language, which I think is appreciated by many that use it.