4 ms·
In Erlang/Elixir, every function from another module is async by design, and the await is implicit. This is by design to allow for hot code reloading. This is
by linkdd 5y ago
In Erlang/Elixir, every function from another module is async by design, and the await is implicit.
This is by design to allow for hot code reloading. This is a very sound design choice because true concurrency is achieved through the creation of other processes (agents, genservers, tasks, ...).
You have nice things like Dynamic Supervisors to monitor those processes. And it works very well.
Try to do the same with uncancellable promises in Javascript/Typescript, or with channels in Go, while it's still possible, you'll find your code growing in complexity way quicker.
The actor model embedded in the language, like Erlang/Elixir, brings so much to the table, it's unfortunate that the author dismiss it without a second thought.
- whizzter 5y agoActually the entire Erlang runtime is "async"(in the terms of the other mentioned languages) almost all the time since it's based on green threads/processes(stuff like a NIF might block of course), the module call is more of a lookup-current-latest-definition call to enable a well defined point for the hot-loading whereas local functions would more likely to be in the middle of a computation (and thus be more likely to be in a complicated-to-upgrade state). async/await being implemented and popularized as it is today everywhere might just be our generations null-mistake since they're so error prone, they're great to show off parallel IO performance increases for classically synchronous languages but the programmer-burden of explicit await's (luckily IDE's/compilers can catch them) with "weird" side-effects was a big mistake. The author of the article seems to be firmly in a specific category of developers so the dismissal seems quite in-character.
- linkdd 5y ago> Actually the entire Erlang runtime is "async" I wasn't sure so I did not want to claim it, so thanks :) > async/await being implemented and popularized as it is today everywhere might just be our generations null-mistake Agreed, especially since each language implements it differently. For example, Javascript's async/await is just syntactic sugar over promises and aren't truly "colored" functions.
- singularity2001 5y agoThe verbosity, hassle and error-proneness makes async/await definitely worse than yesteryears 'goto' failure
- kannanvijayan 5y agoIf you squint closely enough, you can recognize features of the "async/await" discussion in other longstanding issues such as the "effectless/effectful" distinction and whether that should be brought up in syntax, or the "continuations/finite-exits" control transfer distinction (finite-exits being traditional languages which provide a fixed set of control trasnfer such as return/throw/break). The "all things are (posslby) async" is a language design choice similar to "all things are (possibly) effectful" for a procedural language. If you keep your language semantics in the less generalized domain (e.g. functional vs. effectful), you have to invent syntax to talk about the other semantics easily. So you come up with monad systems in functional languages to talk about effects, and make some nice syntax for that. And you have your effectful functions and effectless functions segregated by type signatures that flow all the way up to the top. And you come up with async/await systems in traditional sync-semantics languages, and provide syntax to help with that - whether Javascript or Rust. In both Rust and JS, async functions are distinguished purely in the type-system, with some helpful syntax around it. In both, they desugar to `func(...): Promise<T>` or `func(...) -> impl Future<T>`. Those types too, flow "all the way to the top", imposing a colouring on code. Erlang solved this problem by adopting the general semantics for erlang to achieve erlang's goals, and it seems that the "everything is async" semantics works there (I agree that it's a model that deserves deeper exploration). Languages like Rust can't solve this problem the way erlang solves it, because it takes away from other fundamental aspects of the language. It would necessitate the inclusion of a very specific and very heavy (relative to Rust's expectations) runtime, along with lots of changes to the language that would diminish it along other dimensions.
- whizzter 5y agoInteresting analogy, seems like many of these binary choices cause divides. My point about the design being mistake is the way you need to be explicit in invoking with await and the often harmful side-effect of forgetting the await expression (that in practice becomes a code-path fork that can become tricky to debug if the awaited function is a void function with no unexpected values coming out of the missed await expression). For JavaScript the dynamic type-system really leaves no choice since the caller has no information about the behaviour of the callee, so await for these dynamic languages is kinda unavoidable (unless the entire language is green-threaded thatis). For Rust and C# (and probably others) the compiler knows about this in advance so the "await" expression just adds an extra layer of work for the developer (that is also error prone). There is little reason that for example the C# compiler couldn't have auto-inserted the await for all Task<> returning functions inside every (async) marked function and instead had a (direct) operator for the FEW cases where a developer really wants to handle the Task<> object manually instead of just awaiting it (C# is quite pragmatic in many other cases so the little loss of direct developer control would have been far outweighted by better symmetry and less ceremony). Practical? Definitely, the Zig language seems to even handle this entirely under the hood by it's compile-time functionality. So for a developer the red-blue distinction is mostly something handler by the compiler despite not affecting the runtime more than some extra code i guess?
- deleted 5y ago[deleted]
- bcrosby95 5y ago> In Erlang/Elixir, every function from another module is async by design, and the await is implicit. This statement confuses me. What if I never spawn a process? What if the process I spawn makes function calls into other purely functional modules?
- linkdd 5y agoFunction calls to another module ALWAYS introduce a breakpoint so the BEAM (Erlang VM) can check if the target module is up-to-date, and perform hot code reloading if it isn't. Therefore, every function is interruptible (implicit async), and calls to another module function introduce the breakpoint (implicit await). You always have at least one process, the one running your code. Even if you don't spawn any more processes. And the BEAM still needs to handle signals (like messages, traps, crash, ...) that could be received from another node at any point in time, therefore it needs to be able to interrupt your code, which will be, by design, interruptible. The very nature of Erlang/Elixir is asynchronous by design, even if you don't really see it in the syntax (as it should be, because your concern is the algorithm/data exchanges, not the underlying execution model).