Even if we assumed that (but seriously though, I invite you to at least provide one valid, complete example) your argument is valid, what you're saying is that Go is good for that subset of cases ("some of them") where immutability is the wrong way.
Does not invalidate the fact that it has zilch to offer for the cases where its the right way.
First: I didn't say that Go was good for that subset of cases. In fact, I said that I wouldn't want to write that kind of program in Go. I said that Go wasn't the wrong answer for the reason you stated, namely immutable data.
You want an example? Here's a video router for a TV station, which has multiple sources of user input (human-pushable control panels on two different data buses, plus serial data coming from multiple automation systems). You need to keep those control panels and automation systems updated with what's connected to what, even if they weren't the source of the command that changed it. And you need to keep the actual hardware switch up to date, too. And commands to the switch can fail, which you need to report back to whoever made the command. (One way the command can fail is if someone else locked an output to display a particular input.)
Faced with that problem, we implemented a single "state of the switch" data object that mutated as commands came in. But you could think about trying to implement it with immutable data. That would mean creating a new copy of the state of the switch for each (successful) command that was processed. That would mean copying a fairly large chunk of data many times a second, which would have been a challenge for the processor we had. That would also almost certainly mean a garbage-collected language which, when you're trying to respond within one TV frame (1/60th of a second), is a really bad idea. More to the point for our discussion, it would also mean that threads, which in our design only had to respond to one source of control, would now also have to handle state-of-the-switch updates pushed to them from other threads (or from some master). That seems like significant additional complexity to me. (Yes, I know that data races have their own complexity, but for our design, it was very clear how to prevent that. And if you're going to say that we could have had a separate thread receive the updates to update the control panels, now we've got a race as to who owns the hardware control bus to the panels.)
First, a little correction: you don't have to make a copy of the entire state. This video explains the trick on how to get immutable data structures that share most of their data with their previous version https://youtu.be/SiFwRtCnxv4?t=8m39s - list are straightforward, and vectors and maps are based on the same HAMT tree-like structure.
Of course, a system with real time constrains and hardware limitations will have different optimal solutions. And yes, reference counting is the bare minimum you'd probably like for these structures (GC is even better)
However, we're talking about Go here - a language designed for writing servers that has a GC.
The solution in Haskell is actually quite nice: MVars [1] plus immutable data structure. An MVar contains the current state, represented by one such structure. takeMVar "removes" the variable - a thing which can be done atomically by the updating thread when the data becomes stale. After that, subsequent attempts to readMVar from other threads would block until there is a new updated value, to ensure everything is in sync. Finally, the updating thread does a putMVar, and all readers get the new value and continue executing.
The best part is they don't have to worry that the updating thread might start another update in parallel while they read: the data structures are immutable so the value being read is guaranteed to remain immutable. Even if the updating thread continues "modifying" the new structure in the background, it doesn't have an effect on the other consumer's version.
But yeah, all this is pointless if you have realtime constraints and therefore need super-tight control over execution time. It might be doable in a fast reference counted language, but it will also be much harder to reason about the time it will take to release the memory for the segments that aren't in use anymore.
> The best part is they don't have to worry that the updating thread might start another update in parallel while they read: the data structures are immutable so the value being read is guaranteed to remain immutable. Even if the updating thread continues "modifying" the new structure in the background, it doesn't have an effect on the other consumer's version.
If I understand what you said here correctly, this doesn't work for my example. A thread cannot continue with a stale version (and function properly). It must operate on a current version all the time (or block until it can).
You're right. For your example thats actually an error, and it wont happen if you `takeMVar` before you start working on the new value.
I'm describing a slightly different example there, where its okay to get the old data while updates are being "prepared" (e.g. every item in the dictionary is being fetched from the DB, typical for a server app). In that case, Haskell will work correctly. In Go on the other hand, reusing the data structure may result in a program crash, as Go's built in maps (which might contain that data) are not thread-safe.
(You can't even make the simplest type-safe, thread-safe mutable map that uses a RWMutex automatically under the hood. Because there are no generics)
Does not invalidate the fact that it has zilch to offer for the cases where its the right way.