What Color is Your Function? (2015)
The article discusses challenges in asynchronous programming, critiquing callbacks and promises in JavaScript and Node.js, while noting that async-await improves usability but doesn't resolve fundamental issues.
Read original articlethe fundamental issue of having to navigate between synchronous and asynchronous functions remains. The article uses a fictional programming language with color-coded functions to illustrate the challenges developers face when dealing with asynchronous programming, particularly in JavaScript and Node.js. It highlights the complexities introduced by asynchronous functions, such as the inability to call them from synchronous contexts and the difficulties in error handling. The author critiques the reliance on callbacks and promises, suggesting that while newer constructs like async-await in C# and Dart improve the situation, they do not eliminate the underlying dichotomy between synchronous and asynchronous programming. The piece serves as a commentary on the frustrations of modern programming languages and the ongoing quest for better concurrency models.
- The article uses a fictional programming language to illustrate the challenges of asynchronous programming.
- It critiques the reliance on callbacks and promises in JavaScript and Node.js.
- The author highlights the difficulties in error handling and function calling between synchronous and asynchronous contexts.
- Newer constructs like async-await improve usability but do not eliminate the fundamental issues.
- The piece reflects on the frustrations of modern programming languages and the need for better concurrency solutions.
Related
Synchronous Core, Asynchronous Shell
A software architecture concept, "Synchronous Core, Asynchronous Shell," combines functional and imperative programming for clarity and testing. Rust faces challenges integrating synchronous and asynchronous parts, prompting suggestions for a similar approach.
Synchronous Core, Asynchronous Shell
Gary Bernhardt proposed a Synchronous Core, Asynchronous Shell model in software architecture, blending functional and imperative programming. Rust faces challenges integrating sync and async functions, leading to a trend of adopting this model for clarity and control.
JavaScript Promises from the Ground Up
This tutorial explores JavaScript Promises, emphasizing their role in managing asynchronous code. It covers Promise basics, states, handling methods, event loop, practical usage with fetch(), and the shift towards Promise-based operations for enhanced code quality.
I avoid async/await in JavaScript
Cory argues that async/await complicates JavaScript code, obscures optimization opportunities, increases cognitive load in error handling, and suggests promises are cleaner and more manageable for asynchronous operations.
Understanding Concurrency, Parallelism and JavaScript
The article explains concurrency and parallelism in JavaScript, highlighting Node.js's use of worker threads for efficient task management and the importance of understanding race conditions and performance implications.
- Many commenters express frustration with the "coloring problem" in async programming, highlighting the complexity and confusion it introduces.
- Some argue that async/await improves code readability by making asynchronous behavior explicit, while others find it unintuitive and cumbersome.
- There is a discussion about alternative approaches to async programming in other languages, such as Go and Rust, which some believe handle concurrency more elegantly.
- Several comments suggest that the distinction between synchronous and asynchronous functions should be minimized or eliminated to simplify programming.
- Concerns are raised about the limitations and drawbacks of async/await, with some advocating for more implicit handling of async behavior.
There are serious drawbacks to async/await, and the red/blue blog post manages to list none of them.
I've wrote a blog in response to OP containing a more detailed version of the above:
https://wpbindt.github.io/async/opinions/programming/2024/01...
The “coloring problem” has nothing to do with async at all, it's just about making effects explicit or implicit in the code.
In Go or Rust, returning an error is also a “color” that spreads out to the top of the call stack. Same for checked exception in Java. Unchecked exceptions are like blocking functions, it's invisible so you don't have to manually annotate your functions but as a result any function can now fail without the programmer knowing it.
JavaScript having async/await but unchecked exception may sound bit paradoxical in that regard, but every language has a combinations of both explicit and implicit effects: Rust has both async/await and explicit errors, but it also has panics (implicit errors) and implicit allocation for instance.
I personally live explicit effects, but at the same time when you have too many of them (for example I'd like to have effects like “pure”, “allocating” and “panics”) they start to cause combinatorial explosion of cases in libraries accepting closures as parameter.
Algebraic effects supposedly solve this particular problem but I'm not sure the additional conceptual learning effort is sustainable for a mainstream language, so in the meantime I think every language must keep its amount of explicit effects under a certain threshold.
Once you're in an async function the rules then fundamentally change for how everything operates; it's almost like programming within a dialect of the same language. In particular, I'm referring to everything from function calling, managing concurrency, waiting on results, sleeping threads.
Comparatively, when you're in a go-block in go, you're still writing within the same dialect as outside of it.
But, eventually I realized the difference in pre-emption between languages -- Go can (now) preempt your code in many places, so locks and thread-safety are very important.
The javascript runtime only preempts at certain places, `await` being one. This means I can know no other code can be running without explicit locks around all critical sections.
Finally understanding the trade-offs, I no longer am as frustrated when recoloring a bunch of functions. Instead, I can appreciate the areas where I'm not required to lock certain operations that I would in other languages.
on click
fetch /whatever as json
put the result's data into #some-div
wait 2s
put '' into #some-div
You don't have to mark the script as sync or async, and the runtime will resolve everything for you, making everything feel like synchronous scripting.This obviously has limitations and foot guns, but it works reasonably well for light scripting. More info here:
https://hyperscript.org/docs/#async
And the implementation of it in the runtime here:
https://github.com/bigskysoftware/_hyperscript/blob/c81b07ce...
https://github.com/bigskysoftware/_hyperscript/blob/c81b07ce...
--
[1] - https://htmx.org/essays/hypermedia-friendly-scripting/
This means I can write my tech stack to run in either mode, then swap out async/sync functions in the application. This is exactly what I do in https://Lua.civboot.org#Package_lap
- They're sometimes much too explicit. When writing a complicated generator, I don't necessarily want to annotate every call to a sub-generator with `yield*`, especially if I need to drill that annotation through wrapper functions which aren't really generators themselves.
- Colours show up everywhere. If you have several functions with a `context: GodObject` parameter, then that's a function colour. Most third-party code will be unable to forward a `context` argument to a callback, so you'll have to manually smuggle it in using closures instead.
- Different "colour channels" don't compose nicely with one another. Even though JavaScript ES2017 provided both async functions and generators, ES2018 had to add multiple new pieces of syntax to permit async generators.
- It's normally impossible to write code which is generic over a function's colours. For example, if you have `function callTwice(f)` in JavaScript, you'd need a separate `async function callTwiceAsync(f)`, and a `function* iterateTwice(iterable)`, and an `async function* iterateTwiceAsync(iterable)`, despite the fact that all of those functions are doing the same thing.
Several small languages are experimenting with algebraic effect systems [1], which would make all functions colourless. If JavaScript had this feature, the syntax for defining and calling functions would be the same, no matter whether you're dealing with a normal function, a generator, or an async function. No more `async`, `await`, `function*`, `yield`, `yield*`, `for-of`, `for await`...
This can make everything too implicit and vague. The state of the art is for algebraic effect systems to be implemented in a statically-typed language, which then uses type inference to quietly tag all function types with their colours. This means that the compiler can enforce requirements like "you can only call generators within a scope which is prepared to collect their results", but that rule wouldn't prevent you from wrapping a generator in `callTwice`.
[1]: https://github.com/ocaml-multicore/ocaml-effects-tutorial
Does anyone understand why asyncio-style approach to async won over gevent-style in Python? Why was the former approach accepted into to stdlib, got special syntax and is preferred by the community while the latter is a fringe niche thing?
Another comment: Python's async/await is precisely syntactic sugar for generators. It just changes the keywords and checks that you only use await in a function labeled async.
Edit: my rate limit appears to have been increased.
I may have a sync program running a main loop and I want to have a subset of that program using something like the reactor pattern. In any multithreading aware programming language I can do that by just sticking that in another thread and syncing however I want, even using coroutines I can do that, it doesn't need to follow the reactor pattern.
However libraries (tokio) and languages (js) don't make that the obvious default. In something like JS it isn't even possible, the entire language is built on top of coroutines. For tokio the default is "put tokio main here and voila async" and you need to actually understand what it's doing under the hood to know that that isn't the behaviour you want in all cases.
This is more bottom-up vs top-down learning. Most people learn and teach top-down because it gets you to productive really quickly. Bottom-up is much harder and has the chance of getting lost in the weeds but makes what is happening very obvious.
Does tokio use reactor or coroutines? It depends...
The biggest issue the article describes — inability for sync code to wait for an async result — is limited mostly to JavaScript, and doesn't exist in most other languages that have async and a blocking wait (they can call red functions from blue functions).
If this architectural hurdle is meant to be the color, then lots of languages with async functions don't have it. This reduces the coloring problem down to a minor issue of whether async needs to be used with a special syntax or not, and that's not such a big deal, and some may prefer async to be explicit anyway.
Ruby methods are colorless - https://news.ycombinator.com/item?id=41001951 - July 2024 (235 comments)
On 'function coloring' (2018) - https://news.ycombinator.com/item?id=36199499 - June 2023 (21 comments)
I Believe Zig Has Function Colors - https://news.ycombinator.com/item?id=30965805 - April 2022 (158 comments)
In Defense of Async: Function Colors Are Rusty - https://news.ycombinator.com/item?id=29793428 - Jan 2022 (101 comments)
The Function Colour Myth, Or: async/await is not what you think it is - https://news.ycombinator.com/item?id=28904863 - Oct 2021 (7 comments)
What color is your function? (2015) - https://news.ycombinator.com/item?id=28657358 - Sept 2021 (58 comments)
What Color Is Your Function? (2015) - https://news.ycombinator.com/item?id=23218782 - May 2020 (85 comments)
What Color is Your Function? (2015) - https://news.ycombinator.com/item?id=16732948 - April 2018 (45 comments)
The Function Colour Myth (or async/await is not what you think it is) - https://news.ycombinator.com/item?id=12300441 - Aug 2016 (4 comments)
What Color Is Your Function? - https://news.ycombinator.com/item?id=8984648 - Feb 2015 (146 comments)
No one would want that though right? Like in what context would you even have a consequenceless block? A CLI maybe?
Colorless green functions async/await furiously.
And IT MATTERS. That's why you have structured concurrency hot on the heels of Loom. And once you are using structured concurrency constructs... Ehh, it's not the magic "solution" we were sold anymore is it?
Related
Synchronous Core, Asynchronous Shell
A software architecture concept, "Synchronous Core, Asynchronous Shell," combines functional and imperative programming for clarity and testing. Rust faces challenges integrating synchronous and asynchronous parts, prompting suggestions for a similar approach.
Synchronous Core, Asynchronous Shell
Gary Bernhardt proposed a Synchronous Core, Asynchronous Shell model in software architecture, blending functional and imperative programming. Rust faces challenges integrating sync and async functions, leading to a trend of adopting this model for clarity and control.
JavaScript Promises from the Ground Up
This tutorial explores JavaScript Promises, emphasizing their role in managing asynchronous code. It covers Promise basics, states, handling methods, event loop, practical usage with fetch(), and the shift towards Promise-based operations for enhanced code quality.
I avoid async/await in JavaScript
Cory argues that async/await complicates JavaScript code, obscures optimization opportunities, increases cognitive load in error handling, and suggests promises are cleaner and more manageable for asynchronous operations.
Understanding Concurrency, Parallelism and JavaScript
The article explains concurrency and parallelism in JavaScript, highlighting Node.js's use of worker threads for efficient task management and the importance of understanding race conditions and performance implications.