Hacker News new | past | comments | ask | show | jobs | submit login

Can you specify what you mean by seamless async everything and why it's significant? Are you talking about the execution model here? You mean having a message passing system like in Elixir that allows you to simply construct most of your program by writing callback functions?

Even in a language with no such system like Haskell when you have a web request it runs in its own green thread, allowing you to write mostly synchronous code. I'm not sure what the benefit of some kind of natively async execution would be here.

Edit: formatting




Ok, let's first look at Rust here. Rust has synchronous functions and asynchronous functions. Asynchronous functions return a Future which you can await in an asynchronous function or run in a future executor. An asynchronous function really gets transformed into a state machine with states in the various yield points (mainly awaits, or lower level primitives). Now if you make a blocking - synchronous - call somewhere deep inside then... Great, you've blocked an executor thread for the duration of the call.

Now let's look at F#, Scala, Java, C# (I didn't use Haskell asynchronicity, but I suppose it too has a Task/Future monad). Here you have the same as in Rust with the calling a blocking method deep inside. Other than that, in the Scala that I've seen you end up having Future[T] instead of T everywhere. Which causes you to use monadic functions everywhere instead of using T directly. This is cumbersome and unnecessary in my opinion, because in most applications I'm working on you'll just make 90% of the code async + synchronous helper functions.

Now coming back to Go. There's no distinction between synchronous and asynchronous functions, because there are only asynchronous ones. That's also why Go code will look "blocking" for somebody used to the monadic approach. You don't have to await a function, there's a yield point implicitly inserted at every function call. There's no real blocking in Go. And I'm working on T's all the time, no Future[T]'s.

Sure, it gets hairy if you use cgo, but the scheduler can't reason about C code execution, so you'll block a worker thread for the duration of the call. And there's the proverb "cgo is not go" too.

I'm not meaning to say the other approaches are invalid and the Go one is clearly better. But in practice, in the kind of software I tend to write (microservices, storage systems, service meshes) the Future[T] is just unnecessary clutter which I don't need.

Also, regarding the Elixir parallel, Go doesn't handle everything as callback functions, you just write code comprised of imperative function calls like you would in C (you know what parts of "like you would in C" I mean I hope) and there's no blocking. I agree that Elixir is another example of a language I like by the execution model, but it's much slower than Go if you don't use C-backed libraries.

You also have the point of being able to spawn tons of Goroutines with micro stacks because of how stack growing and moving has been implemented. The main point is that in Go everything is first class green.

Project Loom, as far as I've talked to my Scala writing collegues, seems to be aiming for the same in the JVM ecosystem. Making synchronously written code asynchronous by default. But it's not here yet, so no comparisons to be made.

EDIT: I may seem to be trying to show off with my knowledge of other languages, but people often make a point about developers being too incompetent to use something other than Go as the reason for Go's popularity. For them I'm trying to make a point, that I've been there, tried the approaches, and this really is the one that stuck with me in practice and which I like the most. To each their own.


Alright thanks for taking the time to detail it for me!

Most languages/runtimes indeed don't have this kind of execution model. It wouldn't often make sense for a general purpose programming language to function like this, but then again both Go and Elixir are specifically designed for the web.

You're right in that when there's no built-in design pattern for async stuff the language community finds various, perhaps conflicting, ways of doing it. In Haskell you could use threads or some Async library (that uses threads) to achieve concurrency, there are many different abstractions. But in a typical web API it's not common to be needing lots of async functionality, exactly because each request is already served in its own thread. It's ok to write synchronous code there as it does not block the runtime or any other thread.


Could you please elaborate on the last part?

In a typical web API you don't want each request to spawn a thread, at most a green thread. (at least if you have traffic that requires you to have more than one machine) As I understand the term "green threads", they are scheduled on standard "worker" threads. Synchronous functions will block green threads and this way block the underlying worker thread. If you have green threads, then you need asynchronous functions. (which in Go are just the default, so you spawn a goroutine - or more - per request, without thinking much about it)

And I do actually disagree about the general purpose language part. I think that only really performance oriented languages (like Rust) should go the way of sync/async distinction. Because there's hardly any loss in async-everything otherwise.


By threads I meant the corresponding runtime thread (not OS thread) on each platform. In Elixir they're called processes, in Haskell (green) threads.

Both Haskell and Elixir web frameworks spawn a new thread for each web request they receive. Those threads on both platforms are very lightweight. You end up writing mostly synchronous code on both platforms for handlers that perform the work for those web requests. In some typical smaller API I may not have even a single piece of async code (async statement/expression) on either of those platforms, it's all synchronous in terms of code. It's all thanks to the execution running in its own thread.

I don't understand what possible gain there would be to make everything implicitly asynchronous in this kind of scenario. Haskell already evaluates lazily, and on Elixir you can always just pass messages to other processes. In a typical web request you still need to fetch something from the DB, manipulate the data a bit and then return it. Synchronous code serving a single web request makes perfect sense to me, having everything asynchronous sounds like it'd just make everything more complicated for no reason at all.

Addition: The point of lightweight runtime threads is exactly to allow concurrency without having to write asynchronous code.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: