Here's the essential difference between Go and Erlang: Go gets most of the things right, Erlang gets way too much wrong.
So what does Go gets right but Erlang doesn't:
* Go is fast. Erlang isn't
* Go has a non-surprising, mainstream syntax. You can pick it up in hours. Erlang - not so much.
* Go has a great, consistent, modern, bug-free standard library. Erlang - not so much.
* Go is good at strings. Erlang - not so much.
* Go has the obvious data structures: structs and hash tables. Erlang - no.
* Go is a general purpose language. Erlang was designed for a specific notion of fault tolerance - one that isn't actually needed or useful for 90% of the software but every program has to pay the costs
* Go has shared memory. Yes, that's a feature. It allows things to go fast. Purity of not sharing state between threads sounds good in theory until you need concurrency and get bitten by the cost of awkwardness of having to copy values between concurrent processes
So sure, if you ignore all the major faults of Erlang (http://damienkatz.net/2008/03/what_sucks_abou.html, http://www.unlimitednovelty.com/2011/07/trouble-with-erlang-..., http://ferd.ca/an-open-letter-to-the-erlang-beginner-or-onlo..., http://sacharya.com/tag/erlang-sucks/) it compares very favorably to Go.
You just have to overlook ugly syntax, lack of string type, lack of structs, lack of hash tables, slow execution time. Other than those fundamental things, Erlang is great.
So, let's see you detailed unbiased analysis...
> * Go is fast. Erlang isn't.
Hmm. Given that you accused the author of making biased comparisons, I would expect some more detailed information there. "Faster" doing what? Assembly is faster, Go isn't. Ok, let's use assembly then.
> * Go has a non-surprising, mainstream syntax. You can pick it up in hours. Erlang - not so much.
I think Erlang syntax is small and self consistent. If ; and . are the biggest stumbling blocks to learning a new system. Ok, maybe Erlang is not for you.
> Go is good at strings. Erlang - not so much
Erlang is very good at binaries. It can even do pattern matching on them. Decoding an IPv4 packet is 2 or 3 lines of code only.
> Go has the obvious data structures: structs and hash tables. Erlang - no.
Erlang has obvious data structures -- maps, lists and tuples?
> * Go has shared memory. Yes, that's a feature. It allows things to go fast. Purity of not sharing state between threads sounds good in theory until you need concurrency
Quite the opposite. You can get easy concurrency if you don't share thing between concurrency contexts. You also get low latency response and non-blocking behavior.
Erlang shares binary objects above a certain size behind the scenes as well so those don't get copied during message passing.
It also has Mnesia, a built-in distributed database. It is used heavily by WhatsApp to share data between primary and back-up instances of processes running on different machines.
> You just have to overlook ugly syntax, lack of string type, lack of structs, lack of hash tables, slow execution time. Other than those fundamental things, Erlang is great.
Ok it looks like we only have to overlook syntax. Sound good to me then, I can handle . instead of ; and I will also learn and use Go because both are very cool and interesting tools.
This makes big difference. With pure Go I can do computation heavy things, for example - I've implement some information retrieval methods in Go (indexes, stemmers, ranking and so on). I can't do the same in Erlang, because my map based inverted index, written in Erlang will be to inefficient. I can write it in C, but in my case - most of the application complexity is located in this information retrieval part. Because of that, efficiency is a big deal for me.
We typically don't call that concurrency. Some resource has to be under contention, otherwise its just plain-old parallelism. In Erlang, you have to ship everything to everyone, but the resources are still technically shared...just very inefficiently.
Concurrency is a property of algorithm. Parallelism is a property of the running environment. The hope is given that you have large amount of concurrency, that concurrency would be reasonably and easily distributed over parallel execution units (CPU, sockets+select loops, goroutines, tasks, processes, separate machines, VMs etc...).
So if you have concurrency in your problem domain/algorithm ( say web page requests for example ), and, your language has reasonable ways to handle it. Abstractions like processes, tasks, etc, you can make each request spawn an isolated, lightweight Erlang process (it takes just microseconds and only a few Ks of memory). Then finally at runtime, if you have a multi-core machine or started your Erlang VM with a good number of async IO threads, there is a good chance that you'll get good parallelism too! But if you only run that bytecode on a single core machine, you might not get parallelism but concurrency will still be there.
> Some resource has to be under contention,
Why? That is just resource contention. You don't want resource contention if you can avoid it. Think of concurrent as independent units of executions.
> In Erlang, you have to ship everything to everyone, but the resources are still technically shared...just very inefficiently.
Because it keeps things (including faults, but also logical decomposition) simple. Also it maps well to real life. You have to send messages to other people, emails, phone, image. You don't directly plug a shared network back-plane into their brain. So when that crashes the whole group dies. You want to send a message to them and then continue minding your own business.
And, since other people have already rebutted your statements about structs and hashes, I'll ignore that. †
On needing speed: for IO-bound operations (concurrent ones especially), Erlang is faster than Go. For CPU-bound operations, Erlang knows it can't beat native code--so, instead of trying to run at native-code speeds itself, Erlang just provides facilities for presenting natively-compiled code-modules as Erlang functions (NIFs), or for presenting native processes as Erlang processes (port drivers, C nodes.) If you run the Erlang VM on a machine with good IO performance, and get it to spawn the CPU-bound functions/processes/C-nodes on a separate machine with good CPU performance, you get the best of both worlds.
And finally, on "every program having to pay the costs": is someone forcing you to use Erlang to create something other than fault-tolerant systems? Learn Erlang. Learn Go. Learn a bunch of other programming languages, too. Use the right tool for the job. The article is the rebuttal to people who claim that Go replaces Erlang in Erlang's niche, not a suggestion to use Erlang outside of its niche.
---
† For a bonus, though, since nobody seems to have brought this point up: Erlang has always had mutable hash tables, even before R17's maps. Each process has exactly one--the "process dictionary." It's discouraged to use them in regular code, because, by sticking things in the process dictionary, you're basically creating the moral equivalent of thread-local global variables. However, if you dedicate a gen_server process to just manipulating its own process dictionary in response to messages, you get a "hash table server" in exactly the same way ETS tables are a "binary tree server."
I think lots of people like Erlang's syntax and find it well adapted to what Erlang strives to do. There are certainly people who don't, but like its features, but I don't think its at all true that praise for Erlang is just praise for BEAM (and, yes, the current Erlang VM does have a name, as does its predecessor, JAM).
I mean, Erlang's supporters have made Erlang-to-C and Erlang-to-Scheme compilers, and the main distribution includes a native code compiler (HiPE), so its pretty clear that its supporters don't think that the VM is the only good thing about Erlang.
The only thing thats goofy is the ,;. endings but I just stopped thinking about them after a few months of writting erlang and it became second nature just like everything else.
can somebody reimplement a BEAM in the same way you can implement another JVM?
It's not really "BEAM on the JVM", it's actually a translator of BEAM compiled code to JVM bytecode. But the concept is similar, the target for Erlang is important, but so far a variety of things have been used. JAM was the original VM, later BEAM, later BEAM with HiPE. See [1] for some more information. There was also, apparently, an Erlang-to-Scheme translator. It'd be interesting (does this exist yet?) to see someone implement Erlang semantics in Racket.
Erlang has one of the most battle-tested standard libraries around. OTP is rock solid. It may not be bug free (I don't know that any language can claim a bug free standard library), but it's damn close.
It's pretty obvious you haven't spent much time at all with Erlang based on your points here:
- Claiming "X is fast, Y isn't" is not even an argument, you should've just left that out.
- Arguments about syntax are rather pointless, but Erlang has very consistent syntax, and it's small. You can pick up Erlang in a couple of hours if you are familiar with FP concepts.
- I haven't encountered any real problems working with strings in Erlang. It may not have a bunch of standard library functions for manipulating them, but it's pretty trivial to do most things you would in any other language. It makes up for it with how much of a breeze it is to work with binary data.
- As mentioned previously, structs aren't datastructures, and Erlang has an equivalent (records) for those anyway. Erlang has trees, maps, tuples, lists - I would consider those a lot more obvious and necessary.
- Erlang and Go are both general purpose programming languages. They don't share the same design goals though. You could write a Go program to do a poor approximation of what Erlang is good at, and vice versa, the point though is to use the proper tool depending on the application. I don't know where you got the idea that fault tolerance isn't important for "90% of software", but the software I work on certainly requires it.
- Your argument about shared memory makes it clear you haven't actually used Erlang. The copying of values between processes is abstracted away from you entirely, there simply is no awkwardness. Perhaps there is in Go.
You are claiming the article is biased, but your post is riddled with it. There are certainly problems with Erlang, but none of the things you list are one of them (except perhaps strings).
Erlang strings are quite inefficient if you aren't careful, and their printing is just terrible; a lot of the Erlang community uses binaries where possible instead, since those behave more like you would expect and are generally faster (especially since concatenation can be achieved with io_lists). It's a fair point; most people trying Erlang assume strings are a basic data type (since they were in whatever language they're coming from), they don't know to use binaries instead wherever possible, and so as soon as they see how slow they are, or when they get a non-printable character in it and the entire string prints as a list of integers, it's rather offputting.
For the function and argument orders, there's no explanation for that one.
Erlang strings are a whole other subject, for which I recommend you give an eye to this blog post for the rationale behind a lot of their behavior: https://medium.com/functional-erlang/7588daad8f05 . It's a decent read on the topic.
There are approaches that allow the flexibility of shared state without the possibility of lurking data races or, worse (in Go's case) lack of memory safety. Even JavaScript has such a solution now (Transferable Objects). In fact, Erlang itself has one such approach: ETS.
To be honest, I don't think unrestricted shared state is the right thing in a programming language. It just invites too many bugs (and race detectors don't catch enough of them).
* From what I can tell the times I've looked at it, Erlang's syntax is pretty standard if you're used to functional programming. Sure, if your background is in C and Java, you might have a hard time picking up Erlang syntax, but if you have experience with Haskell or ML, it will be nothing new, except for (that I can tell) its syntax for bit-level operations.
* Structs are not data structures. They are a way to represent objects. Erlang has these as well, in the form of Records, which as far as I can tell are pretty much exactly the same thing as structs.
* Hash tables are only really useful with mutable data, which has its own set of issues and which Erlang does not have (much). Erlang does have maps, which act much the same way but are immutable.
I think it really comes down to that Erlang was designed for a restricted set of uses, which makes it really excel there but seems to hamper it in other areas.
But maps (arbitrary k:v associative arrays) are not, they're useful in all sorts of contexts. And r17 adds EEP43 maps[0] to Erlang (though IIRC only with constant keys at this point)
For instance, there is very little in the toolchain about debugging other than "use GDB". For someone very familiar with the workflow of typing "debugger" in Javascript code, being able to stop the world at any state, examine variables, and having a fully-functional REPL to test expressions, Go's way of "debugging" code is...well. I don't really know how to do it in Go. The general answer seems to be something along the lines of "think about the code you wrote and then write it correctly you idiot."
Seriously, this is the canonical debugging advice, from Rob Pike himself:
"When something went wrong, I'd reflexively start to dig in to the problem, examining stack traces, sticking in print statements, invoking a debugger, and so on. But Ken would just stand and think, ignoring me and the code we'd just written. After a while I noticed a pattern: Ken would often understand the problem before I would, and would suddenly announce, "I know what's wrong." He was usually correct. I realized that Ken was building a mental model of the code and when something broke it was an error in the model. By thinking about how* that problem could happen, he'd intuit where the model was wrong or where our code must not be satisfying the model."*[1]
This is not because the tools are so bad we are forced to bang rocks together. It's because writing code is an effective way to diagnose bugs. Even when I'm in a full-blown IDE with a GUI debugger, I prefer to use printf debugging.
What makes it win is how easy it is to use. What makes it lose is how tedious and manual it is. And how it is restricted to an environment where you are allowed to recompile and load code all the time, and where the output is not going to mess up any other data stream.
I'm starting to phase out printf debugging in favor of tracing, because the tracing tools in Erlang are just infinitely better and more flexible, AND they can work on a production running system without impacting performance in any major way.
I'm able to match specific functions, with specific argument sets. I can see what they returned, and match only specific processes or generations of processes. I can do it from a REPL (because there is one, even in production systems, without needing to run stuff in a screen session), without messing up any related output stream.
Printf debugging is attractive because of how simple and minimally disruptive it is when you're already writing code. As much as it amazes me, it turned out not to be the most convenient tool available for most cases, even if I still use it from time to time.
However, let's say I'm given a third party library, and I know essentially what it does. I want to "see" the properties and methods of the object, I want to "inspect" values, and I want to "test" expressions, all at once. If you're just using printf, then between each of those steps you need to write a line of code, recompile, and reach that state in your program again. Like I said, if you already know essentially what's going on, deep dives like this become less and less important. However, I just think it's a boneheaded philosophy for a language / toolchain to just like...I don't know, assume you're good. I'm trying to become good, but I am getting more and more frustrated with Go every time the default answer to my problem is "be less of an idiot"
Sometimes, however, it's nice to be able to see a live flow of the program and to watch variables change as you step through. I think we'll see more powerful tooling for Go debugging coming through very soon.
I'd love to see more integration in the various editors for go oracle, go cover tool etc. as these would probably show me when I started introducing errors, with the relevant info a click (or key combo) away, before they turned into horrible bugs requiring their own git branch to solve. TDD with the go cover tool is brilliant!
Use cgdb for a nicer interface. Also, you can examine variables and set break points in gdb. Instead of a REPL, you can try out expressions in main(). Your workflow has to change, so if that's a deal-breaker, it is what it is. But if you can change, there are some advantages to be gained.
There is even a C++ REPL somewhere. It basically compiles very time your enter the expression and runs the code. I think it can be done in Go (but someone correct me, if I am wrong here).
[1]: http://play.golang.orgFault isolation and pauseless garbage collection is something that is very important in some contexts. Often the need for it becomes apparent the second time around, after one version of the system has been plagued by large mutable shared state bugs, or strange, un-predictable response times in a highly concurrent system.
Do you pay in terms of raw CPU performance by copying messages and keeping private heaps per lightweight process? Yes you do. There are no magic unicorns behind the scene. That is the trade-off you get for getting fault isolation and soft realtime properties. But keep this in mind, one of the biggest slowdowns a system incurs is when it goes from working to crashing and not working.
Also, no matter how strong the static compile checking is, your system will still crash in production. It is usually a hard bug that has been lurking around not a simple "I thought it was an int but got a string", those are caught early on. It will probably be something subtle and hard to reproduce. Can your system tolerate that kind of a crash in a graceful way? Sometimes you need that.
In the end, it is good for both systems to be around. If you need fault tolerance, optimization for low latency responses, supervision strategies, built-in inter-node clustering(node = OS process or instance of running Erlang BEAM VM) you cannot get that in any other way easily.
Now, one could think of trying to replicate some of the things Erlang provides. Like say build tools static analysis tools to check if go-routines end up accessing shared state. Or say, devise a strategy to use channels-based supervision tree. Heck, if you don't have too many concurrency contexts (processes, go-routines) you can always fall back on OS process-based isolation and use IPC (+ ZMQ for example), as a mailbox. But then again Erlang provides that in one package.
I believe this is what the go race detector does: http://blog.golang.org/race-detector
Also, what is need to made a soft realtime language/runtime?
> To replicate erlang, is easier if used the Actor model? Or can be done with CSP?
There is overlap between then two. CSP is _usually_ synchronous and Actor model is asyncronous. CSP is focused on channels. As in you send messages to a channel and the other side receives them. Channel has an identity. In Erlang you send a message to a process (its main concurrency context). A process has a process ID used to identify it. Like say you have an address at your house and and I send a letter to you.
Goroutines don't have identity. You can't easily send a message to a goroutine. Kill a goroutine, see if it died to start another one and so on.
> Also, what is need to made a soft realtime language/runtime?
Quite simply you need one part of the system to not block other parts from making progress (returning a result). Imagine your serve a page to one user but because some other user is sent some input that take a long time to process, the first user doesn't get a response, he has to wait.
Or say you have some data structures and a common heap between concurrency contexts. A garbage collector might have to stop all goroutines in order to check if it can collect any garbage. So that introduces a pause. Java Azul JVM the only VM with shared that that has concurrent and pauseles garbage collector. It is very impressive, look it up how it works. In Erlang it is easy. Processes heaps are private to each process so they can be collected independently without getting in the way.
Oh I agree 100% here, I had a Java version of a project that went from 15k qps to 60k qps just by switching to the Azul JVM.
That said I still ended up crushing that with Nginx/LuaJIT and I didn't need a proprietary JVM that wouldn't work on some systems because of kernel modules it needed to install etc.
But that comes at the expense of throughput, and it doesn't help with shared state (ETS)
This can also reduce cache misses ping-ponging between CPU cores, including those incurred by a single overarching GC.
As the amount of available RAM grows, in-memory architectures become more desirable, especially for analytics workloads. But how do you do in-memory analytics without shared state? It just doesn't scale.
What we need is a way to make access to shared state explicit, not the default. We also need pausless garbage collection or optional garbage collection so that large amounts of shared in-memory data doesn't cause latency problems.
Pauseless garbage collection conditioned on never using shared state is not good enough for the kind of systems I'm talking about.
Also, I think, all data has to be copied in and out of ETS. It cannot be referenced in-place, which is going to slow things down quite a bit. (Correct me if I'm wrong. I have no first hand experience with ETS)
also, can someone please explain the issues around 'nil' ? i fail to appreciate author's concern about those...
The question can be thought of as two parts:
1) Whether nil should be a base value for objects. If something points to type t, should that be allowed to be nil. It is obvious that it should if coming from C/C++/Python, not so obvious if coming from Haskell for example.
2) Should the lack of objects, or should error conditions be represented by nil. Say lack of a an object in a map is indicated by returning nil. Should it throw an an exception instead? Now, go doesn't have exceptions so it needs something like this perhaps. Or maybe panic should happen.
To expand on 1). If you are coming from Haskell (or any strong typed language) the thinking is, you are already paying the price for static checking, strong types, compilation step etc. It would be nice to have explicit non-nil types. You can say this function accepts a reference to this object and this cannot be nil. During compile time the typechecker/compiler will do a more rigorous analysis based on that, and barf out an error then instead of during production.
Like in Haskell you can explicitly say something may be nil (Nothing):
data Maybe a = Nothing | Just a
I don't personally lean one way or another here. I just don't have a strong opinion either way. I understand the trade-off. But this is how I interpret what the author means by his issue with 'nil'. Hopefully this helps. value, exists = myMap[42]However, null is just a special case of "invalid" pointer (e.g. point to freed memory), and it's very easy to create an invalid reference, e.g. take a reference into a vector and then push a pile more elements onto it, causing the vector to reallocate and move in memory, invalidating the reference.
yes, it does. thank you for your kind explanation.
Take it from the guy who _invented_ nil: http://qconlondon.com/london-2009/presentation/Null+Referenc...
[1]: http://blog.denevell.org/golang-null-nil.html struct foo bar () {
return NULL;
}
is a compilation error as well… (granted it's not an error for strings because C has no strings per-se)For instance: cluster global pids. When you send a message to a pid, you don't have to go through a dance of handling errors and timeouts just because it might live on another node. If the node goes down, there are several ways to handle that, including monitor_node() which gives you a message that you can handle in one place.
I haven't used erlang in production, but I've invested some time to learn about it because I see that potential value.
I don't really see that from Go. It seems to fall into a "general-purpose" bucket where it competes with Java, C#, python, ruby, haskell, clojure, scala, etc. I don't necessarily like all of those languages, and for any given one you can probably pick out some Go advantages. A lot of people say Go hits a nice sweet spot for many applications.
But Go just doesn't speak to any problems I actually have. It can't replace the C/C++ code, because Go has a GC, and the code is C/C++ because it needs to manage memory[1]. It could replace the python code, perhaps to some benefit, but there would still be a bunch of awkward communication between processes and all of the associated error handling mess. And it can't replace Java, because that's a public API we offer, and lots of people are comfortable with Java.
Go should have been another cluster language/VM that really could compete with erlang, in my opinion. To me, Go is just another language.
[1] Rust does seem to have something to offer here.
Rust does have something to offer and I've helped a bit with a thing called oxidize (not the compilation phase in rustc) as an attempt to make a web framework (or at least a routing layer on top of an http lib from the community)
I wonder how this author would feel to compare Erlang and Rust once it matures.
You can write go code that minimizes GC load. It's not like Java or Smalltalk where absolutely everything is a "value" that's really a reference. It's doable to default to pass by value and simply not have lots of pointers all over the place. You won't be able to completely reduce all GC pressure, but it can be minimized.
For data processing, you often want to just load as much of the data in memory as you can, easily 10s of GB. I'm sure there's some theoretical answer from GC folks about how to handle every situation, but in practice it doesn't usually work out well. GC languages target good performance on "normal" applications, where parameter passing and short-term heap allocations are the biggest memory problems you need to deal with.
In Go, you can write your system, such that the GC doesn't have to get so involved with your objects on the heap. Simply use arrays and slices and avoid using pointers otherwise. Slices contain pointers, but if they are pointing to structs in an array that themselves contain no pointers, then you will greatly minimize GC pressure. I suspect you can use the "unsafe" package in Go to do allocations outside of the GC heap, in which case, the GC will not bother traversing there. However, there is the question of whether you would want to do this, as you are giving up type safety and GC and Go has no other facilities like RAII.
Apparently, programmers at Twitter do tasks of the size you describe with Scala on the Hotspot JVM.
http://www.parleys.com/play/514892290364bc17fc56c34b/chapter...
That sounds like a story I've heard a hundred times before. For the cases where it's true, good engineers have already been using one of the many other languages that fills that niche. For cases where that's not the primary problem, Go doesn't help.
That doesn't mean Go isn't a better option for some use cases. Great. I just don't understand why it's worth reinventing the universe to get something a little better for some use cases. Fine with me -- I'm not the one doing the work. I am just saying that I don't get it.
Especially for Google, a company which could have really made a difference in the way we write software.
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
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).
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.
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?
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.)
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.
Preemptive scheduling is good.
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.
Because you can have data races and memory safety issues.
Why is that a surprise? I think it's logical, and Go can be expected to attract developers who are used to garbage collection (Java, Python, Ruby etc.). Not so much C++ developers who prefer to have control and choice (i.e. pay for what you choose, rather than get it handed down forcefully). Rust is a better candidate for attracting more C++ developers than those from Java, Python and Ruby background.
Because it was never the intention. Pike and Thompson, by their own admission, hated C++ and set out to write a better language to replace it. Python and Ruby developers where never on their radar during the whole design phase. It might be logical and obvious in hindsight, but it certainly came as a surprise to the creators of the language.
Many of us that like C++ also do actually like GC systems, but what I personally don't want to give up is the expressive power of modern languages.
Well, they might have hated it, but they didn't understood correctly what its strong points were and why people use it for the projects they use it for. If they did, they'd have made something like Rust.
As it is, they solved problems that mostly people doing backend services in Java/Python/Ruby had.
I find the "right tool for the job" to be a total conversation stopper. It stops the bikeshedding type arguments, true, but it also stops potentially illuminating comparisons. Can we, as a community, agree to stop bringing it up in comparison arguments?
Imagine a new programming language and system being presented in an article. It is healthy and useful for the article to say "We designed system X so it is easier to express Y kind of programs. A, B, C are the complications encountered when doing the same with systems I, J, K." rather than "We designed system X to express Y kind of programs. We like it, but if you don't, use the right tool for your job."
While many of us are polyglots, we do seek to minimize the number of parts when building a system, so such comparisons are often meaningful at some level.
While we're at it, "the right tool for the job" always struck me as a bad analogy. Nobody would question a carpenter's tool choices because the only thing that affects others is the quality of the final result (e.g. how the building will stand up to stress).
But using a software language, framework, library, database, OS, or other platform makes it inextricable from the the rest of the product when judging quality. In some cases, you can make a black box argument like "if a user is unable to observe any poor qualities, then the product must be of high quality"; but that only really applies when you are the sole developer and always will be. For larger projects, there are others involved, and they will be affected if the building blocks are poor.
Granted, that doesn't mean that all discussions about platforms are productive, but it means there is some room for illumination and progress.
There are exceptions to this of course, sometimes something really is inadequate, but for many people a general-purpose language is going to be "good enough". I'm an Erlang fan, but I do think this sometimes hurts its adoption.
I'm curious how people are deploying Go apps in production. Is it nginx+some app server?
(In other words what's to Go equivalent of nginx+unicorn or apache+passenger in the RoR world?)
I personally like the ssl termination that nginx offers. I believe that nginx is a better way to load balance and I like not having to run my code as root, which using a reverse proxy provides you.
I have already right off the bat ran into the concurrency issues even with Go 1.3beta. And the GC locking causes all my connections to drop and thus causes thrashing.
That said I've coded in many languages in my 30 years of development while often reverting back to C over and over again as I've needed the lower level solutions to some big problems. I've enjoyed learning Go and plan to continue because it just "feels right". It's hard to explain but I can see it useful for numerous problem sets and I don't have to dive into C++/Java again to get convenient memory management and hopefully I'll never look pthreads in the face again.
However, I have really been amazed at the speed of LuaJIT. If you would have told me the fastest toolset I could use for my low latency high scale project was an "interpreted language" I would have laughed you out of the room. I did try Python (Cython) and Java and numerous other tools. But so far LuaJIT has turned out to be the fastest, not the most elegant to read but coder time -vs- return on that time is the highest thus far.
I am hopeful that Go will mature in the runtime in ways that will make it compete with Nginx for it's amazing event driven non-blocking architecture. With that in mind I think writing many things in Go will be useful and improvements of the underlying tech will just be magnified by all the code that now needs just a simple recompile to capture those changes. It's like the old days of gcc when I found hand coding some ASM was more useful and now a days it kicks out some amazing code with little need for inlines except in the most extreme situations. Here's to hoping Go traverses that path faster then gcc did and we all will have a more enjoyable time solving the problems we love to solve every day.
This sounds somewhat incredible and unlikely to be a result of Go, more likely to be a facet of a naive implement (we can all break any language). I've built a number of extremely high capacity/concurrency systems in Go to great success, as have many other very large organizations, so the notion that it's just fundamentally immature or broken doesn't fly.
All of the talk about GC in Go is a bit curious, because Go actually makes very little use of GC -- it very heavily favors the stack, versus many other platforms (.NET, Java, others) that use the heap for virtually everything, and turn most everything into an object. The simple fact that Go has a GC doesn't mean that its GC use is the same as all other languages that use a GC.
I’m seeing that I did not make the point of this post clear. I am not saying Go is wrong or should change because it isn’t like Erlang. What I am attempting to show is the choices Go made that make it not an alternative to Erlang for backends where availability and low latency for high numbers of concurrent requests is a requirement. And notice I’m not writing this about a language like Julia. I have heard Go pitched as an alternative to Erlang for not only new projects but replacing old. No one would say the same for Julia, but Go and Node.js are seen by some as friendlier alternatives. And no, Erlang isn’t the solution for everything! But this is specifically about where Erlang is appropriate and Go is lacking.
defer func(){
if recover() != nil {
// tell my parent I died
}
}()
and, Go relies on channels, not goroutines, having identity.