Is it just me or the author fail to explain all the Go's detrimental design clearly? Most of the points he listed there are pretty much personal taste thing and basically what he was saying is "Go has so many problems because Go is not designed as Erlang".
For example, he said
"But when it comes to complex backends that need to be fault-tolerant Go is as broken as any other language with shared state."
Why? Why shared state is so bad in Go? Isn't it taken care by Go's channel anyway?
Also, why Pre-emptive Scheduling is bad? Isn't Error Handling still pretty much just a matter of personal preference?
Why Introspection makes Erlang so much better? What's the practical key problem for Go that can not be tackled without instrospection?
And I completely failed to understand the point of "Static Linking".
I'm not trolling. I don't have Erlang experience, and most of the problem the author pointed out was not bothering me, so I honestly want to see WHY they are problematic in Go
Shared state is bad because it breaks local reasoning. Without enforcing certain disciplines (i.e. always sharing by passing messages via Go channels) it's hard, if not impossible, to reason about local behaviors without considering other parts of the code. Immutable vs shared mutable state is a design choice. Erlang chose immutability for safety, while Go chose shared mutable state for practical reasons, but as a remedy Go recommends best practices to avoid the drawbacks caused by it.
Pre-emptive scheduling is bad because you can have a goroutine running a tight loop starves other goroutines. To address that problem, you should manually call runtime.Gosched() to yield control and allow the scheduler to run other goroutines. Erlang's reduction-based scheduling does not have this problem and can be very fair.
Goroutine lacking identity is a major design difference from Erlang. In Go, channels have identity, but goroutines don't. In Erlang, it's the other way around: processes have identity, and channels are implicit a.l.a mailboxes. In theory you can simulate one style in the other, but the implication of this design choice is very proud. I'm a Go fan, but personally I think Erlang's model is easier to reason about in scale, and it has this nice symmetry with OS threads/processes (you can kill them easily. Good luck killing a goroutine).
There are also a couple technical trade-offs that are imposed by choosing mutable shared-state.
For example, if Erlang processes had shared state, killing a process could get much trickier because you could potentially kill a process while it is changing the shared state, leaving it in a corrupted state. The language would have to work around this by providing atomic blocks or operations to guarantee such won't happen. At the end of the day this would mean more rules the developer needs to keep in mind to write safe software.
Another example are tools like [Concuerror](http://concuerror.com/). It is an Erlang tool that executes your code and finds concurrency bugs and race conditions deterministically. The tool works by identifying all parts where there is communication and shared state in between processes and instrument them. After instrumenting them, it executes the code considering all possible interleavings, which ends up finding the bugs deterministically.
I have been to a workshop and the tool runs extremely fast and its codebase is quite tiny (~2000LOC) because communication and shared state in Erlang exists only on few places which helps reduce the number of interleavings and make instrumentation straight-forward.
However, if you have shared state, every mutation could possibly be mutating a state that is shared in between goroutines, so the places where you'd need to instrument end up being too many, which would generate too many interleavings. You could alternatively try to reject those code paths during instrumentation, at the cost of making instrumentation much more expensive.
> Erlang chose immutability for safety, while Go chose shared mutable state for practical reasons, but as a remedy Go recommends best practices to avoid the drawbacks caused by it.
That's my point. I didn't say it clearly before. I understand why shared state is bad in general. I don't understand if channel takes care of most scenarios why it's still one of biggest problem for Go listed by author.
> Erlang's reduction-based scheduling does not have this problem and can be very fair.
What's the key difference between Erlang's reduction based scheduling and cooperative scheduling?
> What's the key difference between Erlang's reduction based scheduling and cooperative scheduling?
Cooperative scheduling is like reference-counting: you have to tell the compiler when it's safe to context-switch. Preemptive scheduling is like garbage-collection: the runtime decides on its own when it will switch, and you have to deal with making that safe.
Reduction-based scheduling is a hybrid approach, and is, in this analogy, a bit like Objective-C's Automatic Reference Counting. Under reduction-based scheduling, context switches are a possible side-effect of exactly one operation: function calls/returns. This means that it's very easy to reason about when context switches won't happen (so it's not hard to write atomic code), while not having to worry about making them happen yourself (because, in a language that defines looping in terms of recursion, all code-paths have a known-finite sequence of steps before either a function call or a return.)
If you only share state by passing messages you should be fine. The challenge is to make sure the "only" part.
Erlang's reduction-based scheduling basically counts how many steps each process has executed, and automatically switches to other processes once a process has finished a certain number of reductions. So even if a process is running tight loops, it would not starve other processes. Go's scheduler currently cannot promise that.
The article's complaint against Go's scheduling is that it's not fully preemptive. For each Erlang Process (green thread/lwp) the VM keeps a counter for the number of allowed expression reductions, and any process can be preempted after any expression reduction. Preemption in Erlang isn't blocked by loops without function calls.
In contrast, Go preemption is thwarted by loops without function calls.
For example, he said
"But when it comes to complex backends that need to be fault-tolerant Go is as broken as any other language with shared state."
Why? Why shared state is so bad in Go? Isn't it taken care by Go's channel anyway?
Also, why Pre-emptive Scheduling is bad? Isn't Error Handling still pretty much just a matter of personal preference?
Why Introspection makes Erlang so much better? What's the practical key problem for Go that can not be tackled without instrospection?
And I completely failed to understand the point of "Static Linking".
I'm not trolling. I don't have Erlang experience, and most of the problem the author pointed out was not bothering me, so I honestly want to see WHY they are problematic in Go