The different philosophies of "explicitly annotate async functions" vs "implicit async" vs "create os threads with polling" etc will always continue because they all have tradeoffs. So, the "simplification" or a "unification" of a concurrency model always comes at a cost that some programmers don't want to pay. Whenever you see, "Language has solved the color problem so it's not an issue." -- the reflexive skepticism…
What color is your function? (2015)
51–60 of 63 posts
Re: What color is your function? (2015)
#52Even languages like Go and Java with proper fibers don't allow you to yield where you want to, and the yield points are inserted by the compiler/runtime instead.
A use case which I can't seem to reimplement without stackful coroutines is "inline" blocking input, as found in the source code of old roguelikes. It makes the game logic easier to follow since you don't have to rearchitect the entire engine to be event-based, and can instead yield between an "update" and "draw" coroutine each frame.
msg("Do you want your possessions identified?)
local yes = yes_or_no()
if yes then
identify_possessions()
end
I was honestly very surprised when I first realized that this is not how most graphical turn-based games are programmed.Re: What color is your function? (2015)
#53Its new "cps-async" implementation has "auto coloring" as a highlight feature. The Scala compile will just do the right thing in an async context.
Details can be found here:
https://rssh.github.io/dotty-cps-async/
One of the more interesting parts is also that "cps-async" works fine with arbitrary effect monads. There are integrations with Cats' IO and ZIO and some more.
Re: What color is your function? (2015)
#54Zig’s functions are colorblind: https://kristoff.it/blog/zig-colorblind-async-await/
Or is it just stackful coroutines and there is really no difference between async and sync functions?
Re: What color is your function? (2015)
#55Earlier quoted context omitted.
I've been burned by this so many times that I just simply will never use async in any code that is beyond trivial complexity and/or has performance requirements attached. I'll recommend breaking your solution into small pieces that execute separately and pass data to one another via messaging, to isolate the "colors" from one another at the process level.
I'm not a huge fan of async/await (though I have to write a fair amount of it) but that's throwing the baby out with the bathwater. The way to avoid "sync-over-async" deadlocks is to not do sync-over-async, i.e. don't call blocking methods/properties like Wait() and Result on tasks from threadpool threads - it's not what they're for (though they have valid, necessary uses and therefore can't simply be removed). I've…
The whole reason people are trying to build better concurrent/parallel computational abstractions is because they're sick of having to think about that stuff. But to be able to stop thinking about it you need to arrive at abstractions that don't leak: that is, their failure modes should themselves be (non-leaky) abstractions of the lower-level error details. Async/await still forces you to learn about thread-pools and scheduling — while in Erlang one has to learn about scheduling only to optimize performance, and deadlocking requires one to explicitly write a "receive" without a timeout.
Re: What color is your function? (2015)
#56You can call an async from a non async function of course. You just can’t return it’s result synchronously. Nitpick aside, I have got to the point where I care little! Every language has its boilerplatish things, be it Elm’s JSON parsing, GO’s “if (err != nil)” or Haskells myriad language extension declarations. I don’t think asyncs on a chain of functions, or converting a callback function into a promise based one i…
The issue is if you need to add an async call to an existing synchronous code base. If you introduce async in this situation, you’ll probably need to rewrite a huge chunk of your code to allow it. To prevent inconsistencies, this leads to a practice where programmers return promises or tasks by default, even in synchronous functions; which can cause a myriad of problems beyond simply looking really ugly.
block_on(myasyncfn())
This basically repeatedly polls the future returned by the async function, until it completes, and then returns it. (you can also call a full-featured executor like Tokio, but for simple stuff block_on is perfect)My mental model is like this: just like you use await to wait for a future in an async context, you use block_on to wait for a future in a sync context.
You only need to call block_on on the top-level future, meaning that myasyncfn() can have a lot of futures underneath. The only thing that won't work is spawning other tasks (new top-level futures that can continue executing even after the future returned by the function completes). Which is actually amazing: after block_on returns, there is no async background tasks lurking on: the program just continues as a normal non-async program.
I don't know how async is in other languages, but I guess that every language has something equivalent to block_on? How on Earth you integrate async code into sync code otherwise?
As an aside, calling a potentially blocking sync function from an async function is easy too, like this[1]:
spawn_blocking(myblockingsyncfn)
This spawns a new OS thread (or reuse one from the threadpool dedicated to blocking operations), then run the provided function in there. spawn_blocking returns a future that, when awaited, waits for the function in the other thread to complete, and then returns its value.[0] https://docs.rs/futures/0.3.17/futures/executor/fn.block_on....
[1] here the exact function to call depends on the executor you use to run the event loop of your async program, but all major ones provides a spawn_blocking API, like https://docs.rs/tokio/1.12.0/tokio/task/fn.spawn_blocking.ht... and https://docs.rs/async-std/1.10.0/async_std/task/fn.spawn_blo...
Re: What color is your function? (2015)
#57Zig’s functions are colorblind: https://kristoff.it/blog/zig-colorblind-async-await/
Very nice, but how does it support separate compilation? Does the compiler always compile two versions of each function or zig only support whole program compilation ( by JITing for example)? Or is it just stackful coroutines and there is really no difference between async and sync functions?
pub fn write(file: *File, bytes: []const u8) usize {
// Note that this is an if with comptime-known condition.
if (std.event.loop.instance) |event_loop| {
// non blocking version
} else {
// blocking call
}
}
So I guess functions that don't support async could just do something like: if (!std.event.loop.instance) @compileError("X function only supports async");
[1]: https://github.com/ziglang/zig/issues/1778Re: What color is your function? (2015)
#58Earlier quoted context omitted.
> Nope! There's no language magic going on, and the global switch is just a global variable in the root module. It only affects a few functions in the standard library that check that variable. I didn’t say there was magic, I was describing the practical effect of this feature. On a type-theoretic level, it’s like entering a global async monad, which is roughly the structure that Go and Erlang have. But since you men…
>But since you mentioned it, global variables that have side effects are definitely magic, in my view. If I did this in Ruby by monkey-patching synchronous IO methods in response to a global flag, people would definitely call it magic. It definitely is "magic", but it's one of the few areas where I welcome it, personally. What other options are available? The three I know of are function-colored (JavaScript, Python),…
Re: What color is your function? (2015)
#59Earlier quoted context omitted.
Very nice, but how does it support separate compilation? Does the compiler always compile two versions of each function or zig only support whole program compilation ( by JITing for example)? Or is it just stackful coroutines and there is really no difference between async and sync functions?
I believe it's up to the programmer to implement this. Here's some code from the zig github[1] page that illustrates how it might look once zig is stable (this is from an issue). pub fn write(file: *File, bytes: []const u8) usize { // Note that this is an if with comptime-known condition. if (std.event.loop.instance) |event_loop| { // non blocking version } else { // blocking call } } So I guess functions that don't…
But from your example it seems that Zig just do context switching (not knocking it, I'm a big fan of it over await), but then why would it need an await/async keyword at all?
Re: What color is your function? (2015)
#60Earlier quoted context omitted.
This is interesting, it seems like a global mode that kind of turns normal Zig code into Go or Erlang-like non-blocking code. It does mean you have to hold non-local context in your head while reading functions, but maybe that's a worthy tradeoff?
> This is interesting, it seems like a global mode that kind of turns normal Zig code into Go or Erlang-like non-blocking code Nope! There's no language magic going on, and the global switch is just a global variable in the root module. It only affects a few functions in the standard library that check that variable. Zig relies heavily on compile time code executing for metaprogramming, and your code can read symbols…