4 ms·
This is backwards in my opinion and omits a lot of nuance. There is no other mainstream language which combines seamless async-everything-to-the-ground with go
by cube2222 7y ago
This is backwards in my opinion and omits a lot of nuance.
There is no other mainstream language which combines seamless async-everything-to-the-ground with good performance.
Whenever I write in another language I feel like I'm in the stone age only because of this one feature already.
- pjmlp 7y agoC#, VB.NET, F#, Erlang and Elixir come to mind.
- hopia 7y agoAlthough arguably not a mainstream language, concurrency isn't hard with Haskell either.
- cube2222 7y agoI've used F#, it uses computational expressions for async functions. And functions are either async or not. Same for C#. Same for Scala, Haskell and others. Future[T] is so unergonomic and it creates a huge divide in the ecosystem. Been there, hate it. I understand why Rust made the choices it made, they're going with zero cost abstractions, but for other kinds of languages async everything is fundamental to me after having used Go. Erlang and Elixir are not fast, they use C implementations of functions for computationally heavy code, which of course blocks the scheduler for the duration of the call as far as I know and also can bring the Erlang VM down with exceptions. But yes, ergonomically wise they are what I want in this context. Java Loom seems to be trying to add transparent asynchronicity, but it's not here yet. In Go I write straightforward seemingly blocking code, and it just works.
- pjmlp 7y agoIt just works is not what I would call writing into possible closed channels or eventual deadlocks accessing shared data.
- eatonphil 7y agoOr even the simpler panic on concurrent read/write on maps.
- shadowgovt 7y agoIn general, if one is trying to do a concurrent read/write on maps, one his "holding it wrong." Access to map data should usually be channel-gated with a goroutine serving as an accessor to the map data. Mutex-locked if you need it faster for some reason (though chans are already pretty fast).
- cube2222 7y agoWriting to closed channels never happened to me in practice. Sure, you have to write idiomatic go code but then it's a Non-Problem. Deadlocks are indeed a problem but at least there's the deadlock detector which crashes your app with a stacktrace which makes it a minor inconvenience in practice. Being able to launch a goroutine for background housekeeping, like sending keepalives, in a structure constructor without thinking much about it is extremely liberating to me. My point is that Go is not extraordinary in the language constructs it presents. It's extraordinary in its implementation, from-ground-up async and the family of problems it completely abstracts away. I'm not saying you have to agree with me in that Go is the best languag for you to use. But suggesting it's a language which feature wise is left behind in the 80's is plain dismissive and makes you seem uninformed to anybody who's written any nontrivial amount of it I've written code in most of the languages you've compared it against. Even explored heavy functional approaches. And even though they were interesting, often innovative, Go still leaves them behind in the dust in practice for me. Because it hides the stuff I don't care about and works well for creating real-life software which I later have to operate, extend and extinguish when it's burning in prod. Edit: Just to add, implicit interface implementation is great for composing software.
- pjmlp 7y agoI hardly see the difference between, go func () { } and Task.Run(() => {})
- hopia 7y agoCan 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
- cube2222 7y agoOk, 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.
- pron 7y ago> Java Loom seems to be trying to add transparent asynchronicity, but it's not here yet. True, but you can download an EA build and try it today: http://jdk.java.net/loom/ http://jdk.java.net/loom/
- zozbot234 7y agoRust async isn't quite "seamless" (no async in traits) but getting there. Albeit if "seamless" is what you care about, you can always use threads - the overhead is negligible for typical workloads. That's what Go does under the hood; it just uses threads.
- cube2222 7y agoI think there's been a miscommunication. What I mean is that what you do in rust using async/await is done in Go by default for every function and function call. There is no distinction. _there are only async methods in go_ and that is what I want. In other languages, like rust, there is a big ecosystem divide between libraries that are async and those which encourage you to use blocking calls and threading.
- swsieber 7y agoHave you looked at elixir? It's pretty close to async everywhere, though dramatically different than Go I think.
- cube2222 7y agoI've used it, and it's great! However, as I've written in a sibling comment, it's nowhere near as performant without using c-libraries. Which make the schedulers' effectiveness nastier (same in go if you use cgo).
- terminaljunkid 7y agonaive question: What is implementation difference between async and synchronous function that makes many languages differentiate them?
- shadowgovt 7y agoI'm not sure if you're talking about the literal `async` keyword in JavaScript or the general concept of asynchronous functions. I'll answer both questions. Declaring a function `async` in JavaScript causes the function to return a Promise immediately instead of executing all the code within the function (and blocking until that execution is completed). A Promise can be thought of as a "placeholder" for data not available yet; something can later put data into it (which will trigger all code waiting on the promise to have data to be executed). If you call another async function within an async function, you're allowed to use the keyword "await" to block execution of the calling function until the called function either satisfies or fails the returned Promise, at which point the return Promise result is used as the return value of the awaited function and the waiting function finished execution. More generally (speaking loosely), synchronous functions are evaluated to completion before the next line of code is run, and async functions return immediately but either queue up some work to be done later or kick off some work in a separate system (additional thread, additional processor, maybe the network card or graphics card) that happens at the same time your main program body is running. So, for example, "Math.pow(3,3)" is synchronous and your code won't continue until three-cubed is calculated, but "XmlHttpRequeest.open" with its async parameter set (https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/open https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequ...) will return immediately and let more of your JavaScript code run while the browser itself communicates with the network software / hardware to make an HTTP request, fetch some data, and give it to your program; you can register a function to be run once that data is available. ADDENDUM: WHY WE CARE SO MUCH It's generally impossible (I'm lying, but it's a useful lie) for completely synchronous code to "deadlock," which is where there is no way for the next line of code to be run. With asynchronous code, deadlock happens when an asynchronous function is waiting on some result that will never come; the program cannot continue, and (if it blocks your user interface) your computer is frozen. Without some discipline of ordering of events or ownership of task responsibility, deadlock is very easy to cause with asynchronous code. Trivial deadlock example: thread A: start async thread B, wait for a '3' from B, then send '5' to B thread B: wait for a '5' from A, then send '3' to A These threads get stuck forever; A is waiting for B to give it a '3', and B is waiting for A to give it a '5'.