← Back to context

Comment by silverwind

5 hours ago

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

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.

    • I guess it's possible that C# and Java taught the wrong lesson (you can add this later) and that actually reduced the impetus to ensure Go shipped generics in 1.0

      It is also entirely fair to say there's a lot of complexity here and so there's a risk you exceed your complexity budget which for Go as I understand it was very slim. It is a possible a Go 1.0 with more generics doesn't take off because too many people bounce off the extra complexity and so a decade later it's an obscure thing Google made once that has a few fans but not much adoption.

      Or that extra complexity means Go 1.0 ships five years later, after Rust 1.0 has given people an appetite for better performance and better safety and its sharpest corners have already been knocked off.

      2 replies →

    • Maybe at some point it may even get the ternary conditional operator or a proper exception handling syntax.

      Those things that Golang defenders say "dont make sense" until it's implemented, then 'it always made sense" and of course it's a good idea.

    • So your argument is that they should have gotten it exactly right first time and stuck to their guns?

      I mean come on. The Golang team created a useful language people use for building real things — it’s easy to work with especially on large teams, and when the lack of generics turned out to be a pain point in the end (after years of production reality) they understood what the community wanted and actually… added them..

      Now what, it’s not good enough?

      No one forced you to use go.

      No programming language is perfect. I personally find the language has served me well.

      And after being so very pro generics myself, i actually find myself not even really using them that much apart from calling the slices module etc which has them under the hood anyway…

      3 replies →

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

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.

    • Often the mutation might have performance benefits.

      Of course if you're Rust the stdlib and compiler might conspire to optimise an operation you wrote which reads as non-mutating into an actual mutation which was faster.

      This is another benefit of the "destructive move". If I consume X and spit out Y, the X is gone, so it's OK if secretly I just mutate X and tell you that's Y now.

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