Hacker News new | ask | show | jobs
by tptacek 4242 days ago
That's a reductive summary of goroutines. I don't doubt other languages have competitive features, but let's be clear about what the feature is:

* Lightweight threads with a scheduling and state overhead low enough to run hundreds of thousands of threads at a time.

* Seamless integration with the language, without any rituals or incantations needed to invoke them; you can, for instance, trivially pass closures from goroutine to goroutine.

* A data sharing scheme that makes sense with promiscuous threading ("synchronized" data structures often don't, because they'll end up serializing threads)

Lots of languages have green threads, but not all these properties.

I'm guessing Haskell does? I don't write Haskell, but my impression is that it has everything.

4 comments

Erlang has the first two concepts nailed down.

I don't know what "data sharing ... with promiscuous threading" is, but Erlang is functional and immutable, so data sharing only exists in the sense of passing immutable data structures around. Synchronization issues associatied with mutations don't exist for in-memory structures since it's all read-only access.

I don't know Haskell's concurrency tools very well, but I believe it can provide much of the same functionality as Erlang, but the whole OTP toolset is missing — just as it is in Go. (Certain aspects of OTP can't be implemented in Go at all without additional changes to language/runtime.)

Neat. You can start an Erlang process (or whatever they're called) with an anonymous function, and pass it another anonymous function that closes over another lexical environment?
Yes, both in Haskell and Erlang.

For me, Go's big win is that I can finally bring this, the right way to do it, to work, because it's the first time this paradigm is present in a conventional imperative language, not that it is the first in general. Many other languages got it right in theory before Go, but there was always some practical stopper, involving some obscure programming paradigm that I stood no chance of getting buy-in on (from engineering or management) or an ecosystem that simply couldn't be reasonably be called production-ready across the set of tasks I needed to accomplish. Go finally gave me almost everything I wanted for work. If it isn't everything, well, that's life sometimes, you know?

Like Java's strategy (http://www.win.tue.nl/~evink/education/avp/pdf/feel-of-java....)

  Java is a blue collar language. It's not PhD thesis
  material but a language for a job.  Java feels very
  familiar to many different programmers because we
  preferred tried-and-tested things.
Absolutely:

    do_stuff() ->
      GetAnswer = fun() -> 42 end,
      spawn(fun() -> io:format("The answer is ~p\n", [GetAnswer()]) end).
Here, GetAnswer is a closure, as is the anonymous function given to spawn() function.
And here is why these functional langauges will never make main stream.

I've, been programming for 20+ years and I look at that code and say what the f*k?

Ooh, it's the functional stuff. Ok, move on.

Seriously? I mean, Erlang has some gnarly syntax, but this is really basic, and translates directly to anything you'll find in JavaScript, Go, Ruby, Scala, C++, even Java.

Let me take you through it:

    do_stuff() ->
This declares a function do_stuff(). It's exactly like:

    function do_stuff() { ... }
Everything after is the body. Unlike many languages, Erlang uses comma, not semicolons, as statement separators, function bodies end with a "." so a function follows the form:

    do_stuff() -> a, b, c.
Here, a, b and c are statements. The last statement provides the return value. This directly translates to:

    function do_stuff() { a; b; return c; }
Next line defines a variable:

    GetAnswer = fun() -> 42 end,
This just defines a variable which is an anonymous, inline function, sometimes called a closure or a lambda (all three are technically correct). In Erlang, anonymous functions end with "end", not ".". This is equivalent to JavaScript:

    var GetAnswer = function() { return 42; };
The next line is therefore easy to understand, as it uses the same inline function syntax; it calls spawn() with this function as an argument. So it's this:

    spawn(...)
The argument is this function:

    fun() -> io:format("The answer is ~p\n", [GetAnswer()]) end
JavaScript version:

    function() { return io.format(
      "The answer is ~p\n", [GetAnswer()]); }
(Of course, JS doesn't have spawn() or io.format(); this is just syntax.)

Complete version:

    function do_stuff() {
      var GetAnswer = function() { return 42; };     
      spawn(function() {
        io.format("The answer is ~p\n", [GetAnswer()]); });
    }
In some ways, the JavaScript version is actually gnarlier. Look at all those braces and semicolons.

The thing is, the syntax we found nice is usually nice because it's familiar. Many languages could seem like an incomprehensible mess if you're used to C-style brace syntax. But that's purely a question of familiarity. If you don't know what all the bits and pieces mean, you're going to be alienated by it. A developer who has grown up on COBOL and Forth will not find JavaScript syntax familiar any more than you find Erlang syntax familiar.

I suggest stepping out of your comfortable protective shell and trying it out. It's not rocket science. After 30-60 minutes reading this book you'll no longer find it alien, I bet:

http://learnyousomeerlang.com

Also don't ets tables do that, #3 I mean? Where different processes can access the same data structure? You can even control what process read or write to it. It is pretty neat.
Yeah, you could have said the same about Haskell. 'forkIO' in Haskell and 'go' in Go are essentially identical. You could also argue that Haskell's STM is even safer than using CSP channels (which also exist in Haskell) since you greatly decrease your chance of data races (while sacrificing some performance in cases where a transaction is often retried.)

But in the end it doesn't really matter that much. Go and Haskell are completely different languages for different purposes (getting stuff done in regular companies vs. doing interesting PL research or more advanced development in companies with little turnover.)

>You could also argue that Haskell's STM is even safer than using CSP channels (which also exist in Haskell) since you greatly decrease your chance of data races

We actually have transactional and non-transactional channels as well, which is pretty cool.

Also the reason for using STM instead of channels is that they compose without the possibility of deadlock. You can have deadlocks with channels.

I'll try to represent Haskell here.

1. Totally, definitely. The new Haskell IO manager in GHC 7.8 completely blows this out of the water. It's wonderful.

2. You fork or async IO procedures which can be considered immutable values in their own right and passed around without the slightest concern. There is a small difficulty in that Haskell's laziness makes it a small trick to ensure that work gets done in the right place, which is undoubtedly a mark against Haskell, though perhaps not a huge one.

3. Things like STM, MVars, and (now) LVish let this happen in all kinds of ways. The only problem might be picking the right option due to the wealth of choices. Fortunately, the right answer is almost always STM.

I'd be more than happy to translate a task to demonstrate these things.

> The new Haskell IO manager in GHC 7.8 completely blows this out of the water.

Go will still outperform Haskell by a huge margin and the typical Go process will have a much smaller memory footprint.

I don't even like Go, but nobody choosing Go is realistically considering Haskell as an alternative.

I encourage you to look a the benchmarks published here: http://haskell.cs.yale.edu/wp-content/uploads/2013/08/hask03... which includes Haskell beating nginx and Haskell beating SDN controllers written in C++ and in Java.
I'm not so sure that is true. I'd love to see some benchmarking, though.
I don't like go either, but I won't believe that a ghc webserver is a fast as a go webserver until we can see it in the techempower benchmarks.
The Techempower benchmarks are using the old version of GHC, 7.6, and the Golang devs have been tuning Go's core libraries to win at this specific benchmark.
Even then, it is only a factor of 2 slower. That is next to nothing.
Right, precisely, and this is at a very silly tier of microbenchmarking where you're doing little to no actual work, something you virtually won't ever find in the "real world".
> The Techempower benchmarks are using the old version of GHC, 7.6

7.4, last time I checked.

Clojure has all of this in core.async - which is just a library, it doesn't need special support from the underlying language.

goroutines are certainly nice and CSP is a good mental model for solving many problems, but I don't think a feature like this should be used to justify switching to a whole new language.

What? No. You could not be more wrong. core.async does not support the first point in the least.

Don't believe me? Make a go block in core.async and have it call another function, then have that function block. Do it 10,000 times. I'll wait. And wait. And wait. And wait...get the picture?

Clojure has this in Pulsar, which is a library...that happens to perform bytecode modification to support this (which I consider to be on the same level as special support from the underlying level). Pulsar is an absolutely amazing project and I'm not trying to take anything away from it with this comment, but merely trying to illustrate how far off you are.