5 ms·
What I like least about this article is that it's completely soured the entire context of asynchronous programming. Invariably, any time someone discusses desig
by jcranmer 5mo ago
What I like least about this article is that it's completely soured the entire context of asynchronous programming. Invariably, any time someone discusses design of an async functionality, function coloring is brought up, with almost no analysis as to how it applies and why it's a good or bad thing. (Ironically, I probably see more in-depth analysis these days as to why this article isn't apropos than why it is when this happens.) It's just reduced to "anything that makes a separation between async and sync is function coloring and that's automatically bad." The existence of any sort of trade-off, or really, the entire meat of the article, is just completely ignored.
One thing that can be better called out is that this issue of function coloring isn't just an async problem. Exceptions cause function coloring--and not just Java's controversial checked exceptions. An infallible/fallible domain split is function coloring. Javascript's async handling is called out not because it's doing the function coloring but because--in 2015--the tools that existed for dealing with async code in JS libraries were really, really bad, largely reliant on callback hell. Promises and the async/await keyword fix most of the issues, and the ones that aren't fixed boil down to the fundamental issue that an asynchronous event-loop model and a synchronous batch model are just different programming paradigms to begin with.
- smcg 5mo agothis article seems like a nice view into what things were like in 2015 but we've come along way since then.
- dnautics 5mo agowhat I like least about this article is how people seem to just substitute some other, usually theory, notion of what function coloring is to elide the argument (usually to excuse their favorite PL) without actually RTFA. The article is about ergonomics, not PL theory. > Exceptions cause function coloring do they? Do they? 1) Every function has a color 2) The way you call a function depends on its color 3) You can only call a red function from within another red function 4) Red functions are more painful to call 5) Some core library functions are red
- deleted 5mo ago[deleted]
- nemothekid 5mo agoJava's checked exceptions fit the 5 criteria: 1. It either `throws` or it doesn't 2. If the function `throws` you have to wrap it in try/catch, or make your function `throws` 3. Your function is `red` if it `throws` the same exception. 4. see (2) 5. See the FileReader class in core. Now, C++ exceptions might not satisfy all of these, but the problems CheckedExceptions were meant to solve still exist in C++ and as a result some style guides forbid them entirely. Like async, the biggest problem with exceptions were the ergonomics.
- dnautics 5mo agoOk, sorry it's been about 20 years since I last javad IIRC you didn't have to declare exceptions in your function signatures. However, wrapping in try/catch seems to violate #3. Try catch is not a heavy lift of a seam between red and blue To be fair, #3 seems to have shades of grey. In some pls, you can call an async function from a sync one by wrapping it in a whole damn event loop system. Should that count?
- Terr_ 5mo ago> Ok, sorry it's been about 20 years since I last javad and you didn't have to declare exceptions in your function signatures. You're probably remembering RuntimeExceptions, which are a subgroup [0] that are exempt from "checking" by the compiler, which means it does not require method signatures to declare "I might emit this." [0] https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/lang/Exception.html https://docs.oracle.com/en/java/javase/26/docs/api/java.base...
- vips7L 5mo agoYou can declare your runtime exceptions too. The compiler won’t enforce you to catch them though. void fn() throws IllegalStateException
- tardedmeme 5mo agoWhat I like most about the article is that it drove the conversation to realising that async is just a poor reimplementation of threads, and put the focus back on how to do threads faster.
- dnnddidiej 5mo agoProbably you are right except for some pathological scenarios. Threads and green threads and models where you have have 10000s of threads and not even hit the cardio fat burning zone.
- dnnddidiej 5mo agoI am so happy I have never heard anyone IRL say colored functions. It would annoy me. The concept is interesting but like all engineering it is a trade off. In Node amd Go you don't get a choice anyway. In C# you might choose based on performance thinking of thread pools etc IIRC. When programming in Node I find in practice async and "colored functions" no issue especially with async await. Except for performance issues they come with sometimes but not at a programming level.
- rtpg 5mo ago> When programming in Node I find in practice async and "colored functions" no issue especially with async await. Except for performance issues they come with sometimes but not at a programming level. JS solves this problem in two ways in the ecosystem: - basically saying "all functions must be async" in practice - allowing you to await a non-awaitable ("await 3" is valid) so library authors can "force" async/await, but users don't actually have to interact with it when they don't need to. But "everything" being async/await means it's all 'basically fine' anyways There's also the fact that JS libraries tend to be "pass in a bunch of callbacks" vs, say, Python's "override this class". It makes it much easier for libraries to have everything be async and have it really not get in the way. Python libs tend to have much larger API surfaces due to how OOP works. So async-y internals works are harder to isolate cleanly without breaking the public API. But if you make your API "async-first" then the debugging experience in Python is miserable (try pdb'ing your way through awaitables....) Even here though there are problems. For example, I've tried in the past to replace some lib with a more performant WASM-y thing. But it couldn't be a drop in replacement because the original library was a sync-only API, and the replacement was async! Something very silly: you write "function add(x, y) { return x+y }". A bunch of people do things like "add(add(x, y), z)" everywhere. You find out you could make "add" "better" with async/await. You now have to get all callers to rewrite. So what everyone does is just throw _everything_ in to the async/await pile. Which... I guess is fine but I personally dislike writing "await add(await add(x,y)), z)". (aside: Rust's postfix await at least makes this kinda refactor less annoying)
- leonidasv 5mo agoThe Node world was built with asynchronicity in mind. First via callbacks, then Promises, then async/await (Promise-based), so it feels natural now. But if you take Python (for example), it's a shitshow. You usually have two versions of the same API, split by function name, client, package, or namespace: `foo` and `afoo`, where the a-prefixed one is async and meant to be used inside async function call chains, and the other one is the blocking version for non-async chains (which are still very much in use). It's a pain to develop for, to maintain, to scale, everything.
- gpderetta 5mo agoThe problem with function color exists when you can't abstract over it[1] Some statically typed languages (I believe both Haskell, ocaml) have powerful type system that allow abstracting over function types and function colors. Color is not an issue here. Some other statically typed languages (C#, rust, and C++ (at least with the built-in stackless coroutines)) can abstract over types but not over colors. This is a problem. Some statically typed languages (Go) do not encode async-ness statically, so it is not an issue[2]. Some dynamically typed languages (scheme, Lua, lisp) also do not encode async-ness statically. Everything is fine. Finally there are some dynamically typed languages (python, js) that, eskew static types but for some reason still decide to encode async-ness statically. For me this is the most bizarre decision, especially as some of the justifications for static async-ness (performance, memory usage) are less relevant. [1] for example, a litmus test is being able to implement an higher order function that inherits its color from one of its parameters. Essentially this is the problem of generically turning an internal iterator to an external one. [2] fundamentally in these languages continuations are first class values that can be passed around, so the asyncness is naturally not bound to the function that created a continuation. Edit: you can in principle abstract away color in any async language by simply assuming that any function call is async and await it. Then sync functions can trivially be made async. But at this point async annotations no longer convey any useful property: the language might as well implicitly await any function and require call-site annotations for diverging control flow or reentrancy requirements. More practically as languages with async evolve and grow asyncness becomes pervasive and pushes away any sync component.
- akst 5mo ago> The problem with function color exists when you can't abstract over it Hopefully it's safe read this as there's no common static type between function and async function meaning APIs (that take functions as arguments) have to provide seperate methods (or overloading) for these different colours. Like in typescript you can write `<T>(f: () => T) => T` because an async function statically is just the return type wrapped in a Promise, not something like `async () => T` you can still pass in an async function as an argument. I think that's a reasonable thing to take issue with, and its _possibly_ an avoidable design problem. That said I can see it being less avoidable if the async function requires some special kind of invocation (like being associated with some kind of async runtime and its a compiled language). When I see people bring the issue of function colouring, the focus tends to be on the fact that a function is no longer interchangeable with a sync function and now you have to handle a promise, which I personally find unconvincing if the return type really should be a promise then it shouldn't be interchangeable with a sync function.
- EGreg 5mo agoActually, it's not async programming. It's only async programming the way JS does it... which is unwinding the entire stack, and then starting another stack on the next tick. Instead, many languages have fibers / coroutines / etc. which simply start new stacks elsewhere, and capture the context.