Related:<p><i>What color is your function? (2015)</i> - <a href="https://news.ycombinator.com/item?id=28657358" rel="nofollow">https://news.ycombinator.com/item?id=28657358</a> - Sept 2021 (58 comments)<p><i>What Color Is Your Function? (2015)</i> - <a href="https://news.ycombinator.com/item?id=23218782" rel="nofollow">https://news.ycombinator.com/item?id=23218782</a> - May 2020 (85 comments)<p><i>What Color is Your Function? (2015)</i> - <a href="https://news.ycombinator.com/item?id=16732948" rel="nofollow">https://news.ycombinator.com/item?id=16732948</a> - April 2018 (45 comments)<p><i>What Color Is Your Function?</i> - <a href="https://news.ycombinator.com/item?id=8984648" rel="nofollow">https://news.ycombinator.com/item?id=8984648</a> - Feb 2015 (143 comments)<p><i>What Color Is Your Function?</i> - <a href="https://news.ycombinator.com/item?id=8982494" rel="nofollow">https://news.ycombinator.com/item?id=8982494</a> - Feb 2015 (3 comments)
I often hear that if a distinction exists, it should be represented in the type system; functions are either async or not, so we should track it at compile time.<p>The thing most people don't understand about language design is that if you follow this line of thinking to the extreme, your complexity goes through the roof as all of the infectious constraints spread upward through your call graph, sometimes conflicting with other constraints.<p>Rust is an interesting data point. It already has at least three infectious mechanisms, which spread constraints upwards (async vs synchronous, Sync vs !Sync ("data coloring"), &mut and borrowing in general). If you've ever been unable to propagate these infectious constraints upward because you couldn't change your signature (because it was a public stable API, or it was a trait override, or it was a `drop` method) then you've felt this complexity first hand. I believe this is the main reason that we see less (healthy) abstraction in Rust compared to other languages.<p>Instead, I think a language should use these kinds of "infectiousnesses" <i>very</i> sparingly. In this order:<p>* Find a solution that doesn't involve infectiousness. I think Loom did really well here.<p>* Add an escape hatch (not one that stalls the entire async runtime, preferably). For example, interfaces are a good escape hatch for static types.<p>* I very much like MrJohz's suggestion in [0]: invert a feature's infectiousness by changing the default color.<p>If I had one PL-related wish, it would be for a mechanism that's non-infectious like Loom, but didn't involve its stack copying and didn't need a runtime. I have a few ideas along those lines, but we'll see if they pan out.<p>[0] <a href="https://www.reddit.com/r/ProgrammingLanguages/comments/vofiyv/comment/ied4gaw/?utm_source=reddit&utm_medium=web2x&context=3" rel="nofollow">https://www.reddit.com/r/ProgrammingLanguages/comments/vofiy...</a>
> Green threads aren’t easier than async functions. The function colors don’t go away<p>Don't they? Using green threads I can happily call a blocking function in a synchronous context in one place, and run it with a goroutine in another place.<p>With async (particularly Rust's tokio), you need to pass around the "runtime" object in order to call an async function in a synchronous context.
> The less interesting version is that you do always have to option to just block. You can call a task runner on that future, and synchronously block until that specific future completes itself. This isn’t the greatest approach, because we probably don’t want to block, but it is an option.<p>My first thought, coming from a JS perspective, was “no, you actually can’t do that”, because blocking the event loop will prevent resolution of the Promise. My next thought was that this is particular to a single threaded, cooperative concurrency model, so of course “can’t” is situational.<p>But on further thought, it’s much closer to my first thought: you <i>can</i> do that if you can interleave asynchronous ticks with synchronous ones, but only if you can determine there are no data races (or accept them if there are). This is something I suspect Haskell (or Rust?) is probably well suited for, but for JS it’s a complete non-starter.<p>Another comment made the point that the async keyword (the explicit “color”) is at issue, and I nearly made this comment there because it raised the same concerns for me from a different angle: if you can “await” from an otherwise synchronous function, you’re either accepting data races, blocking permanently, or implicitly “coloring” all functions as async with the same implication that you can statically eliminate races.<p>At which point, writing this, I realized this would all be so much simpler if everything was a statically analyzable DAG with transactional semantics.
It would be nice to mention in the Haskell part the solution which is just use 'trace', which just prints the thing and also evaluates to it, so it's just a pair of parens and a keyword and away you go.
I think it's all a matter of how you want to draw the line: do you want to expose the underlying runtime to the consumer, or do you want to make green threads look like real threads? I'm more sympathetic to the latter, because real threads with kernel scheduling don't have function coloring and are even less predictable in when they yield, so why can't my language runtime (e.g. in Python) just pretend they're kernel threads?<p>I give a pass to Rust for this one though, since it would need a more heavyweight runtime with green threads to deal with this.
This article conflates two things:<p>1. A real limitation of JS that prevents it from using results of async functions in sync functions.<p>2. What syntax author prefers for awaiting async calls (here: none)<p>This makes "colored/colorless" discisions confusing, because most languages have fixed #1 and don't have any serious deficiency there, but also most languages don't have hidden implicit await #2 — often on purpose, because await has performance implications, changes semantics of locks, and becomes a leaky abstraction around FFI.
> You can only await for it from an async function.<p>get rid of this needless requirement and function coloring goes away.<p>If you await, you should be able to return the raw type directly, without needing an async keyword.