4 ms·
I think you read the article as saying that juggling function color is trivial nonsense and a waste of programmers' time, and your rebuttal is "await" isn't tri
by Calavar 2y ago
I think you read the article as saying that juggling function color is trivial nonsense and a waste of programmers' time, and your rebuttal is "await" isn't trivial - it conveys important information about the function.
My reading of the article is very different. I think the main thrust is function color is infectious, and that's an issue because adding "async" to a single function can have cascading effects through your codebase, which makes refactoring difficult and slows down development speed. This is true even if "async" conveys important information.
- eyelidlessness 2y agoThis. Plus, the cascading effect isn’t necessarily just difficult to account for, sometimes it makes integration effectively impossible. As an example, a project I work on provides functionality implementing arbitrary computation graphs, with an explicit guarantee that all computations are synchronous, consistent and complete on return from every state change call. This guarantee is a conscious design decision that characterizes both the project itself (and its API boundaries) as well as many of its internals predicated on the same principles. It does explicitly account for asynchronous behavior at the entry point, and at very specific exit points. But introducing async anywhere else in between would mean building a fundamentally different project, and probably revisiting business goals in the process. The function color problem isn’t just annoying or tedious, it can be fundamental to whether certain projects are even viable.
- fwip 2y agoTo me, this actually reads as a reason for function coloring. Your project makes guarantees that neatly match up to the division between async and sync code, and it would be fundamentally incorrect to ever call a function marked as async from most points of your program flow. So in this case, it would be good that the language enforces this for you, right?
- assbuttbuttass 2y ago> it would be fundamentally incorrect to ever call a function marked as async from most points of your program flow For a language with async/await yes, you need to bifurcate your program into synchronous and asynchronous pieces, and be very careful to maintain that split. For a language with a synchronous thread abstraction like go, you simply don't need to create this artificial bifurcation in the first place
- fwip 2y agoYes. The person I replied to described a situation that, to me, is made easier by this bifurcation.
- eyelidlessness 2y agoIs it though? In a hypothetical JS with the possibility of, say, “decoloring” async functions—e.g. by optionally blocking on their resolved return value—the situation that function coloring helps… ceases to exist as a situation in need of helping in the first place. That would be broadly unappealing where single threaded, cooperative concurrency is a baseline assumption! But it’s more fair to say it’s a trade off, and it’s also fair to recognize the things made impossible by a trade off.
- eyelidlessness 2y agoWell, yes and no. Yes: because the language has distinct semantics for asynchrony which fundamentally* cannot be made synchronous, it is good that the language also makes that fundamental inability explicit, and inviolable. No: the above reasoning is inherently tautological! It’s effectively arguing that it’s good that the language doesn’t have a massive inconsistency between two deeply coupled aspects of the same semantics. That is good! But it’s the kind of good you kind of have to expect, or treat as a tragic design flaw in its absence. * There are some exceptions, but they’re generally esoteric or niche or have other important caveats.
- daotoad 2y agoSo, this cascading issue is hitting me right now. Being real vague here, in order to support a new capability, we need to insert an asynchronous operation in the middle of what has been a synchronous operation. The synchronous operations is done implicitly and explicitly in many, many places in the app (yes I know this sucks, big old codebases are what they are, and you can only refactor so much at one time while continuing to deliver new features). We have a hard ordering requirement where the new async behavior must be completed before the work can continue. And it's killing us. What would be a simple case of "call this new function" is spiraling into weeks of work and thrown our planning into question. Most of the time it's pretty easy to accommodate the contagion. It's a little more work than is ideal, but it is fine in practice because you hit a natural boundary that stops the spread. But in this case, due partly to tech debt and partly due to deep architectural decisions, it's blocking our ability to move forward in a reasonable amount of time. We will certainly find a solution, but it's going to be much more expensive to get there than it needs to be.
- wesselbindt 2y ago> I think the main thrust is function color is infectious My response to this would be that this infectiousness is not a feature of async/await but an intrinsic property of the function. A function calling an expensive function is itself expensive. This is not a bug, it's just reality, and async forces you to be upfront with that. Another part of my response would be that it doesn't need to infect your entire code base, and that it actually pushes you in the direction of keeping your business logic (which can be sync) separated from your infra logic (which is almost guaranteed to be async). This too I see as a good thing, mixing business logic with infra logic tends to push you in the direction of death by a thousand cuts bad performance. You can achieve this separation as follows: async def your_endpoint(): business_object = await db.get() business_object.do_business() await db.save(business_object) The blog post I linked to goes into more detail.
- Calavar 2y ago> My response to this would be that this infectiousness is not a feature of async/await but an intrinsic property of the function. A function calling an expensive function is itself expensive. This is not a bug, it's just reality, and async forces you to be upfront with that. You're right, the async keyword describes a reality about a function, and any attempt to syntax sugar your way out of that reality will leave you with a leaky abstraction. Async is async whether you have colored functions or colorless functions. On the other hand, should changing a single function from synchronous to async require you to touch up your code in 30 different places across 10 different files? If you know what you're doing and you're okay with the async call, why do you have redeclare your intentions over and over again like you're punching in nuclear launch codes? It's an opinionated question. You can see a similar design tradeoff in Zig's approach to generics (implicit type constraints) versus Rust's approach to generics (explicit type constraints) Rust's explicitness removes potential footguns, but it's also an absolute PITA to refactor your type system when you have to.
- wesselbindt 2y agoI do understand that the point the blog is making is the infectiousness. My point is when you drop the meaninglessness of blue/red and replace it with the more meaningful async/sync, this infectiousness goes from being arbitrary and annoying to completely natural, and even desirable. If all of a sudden you find yourself wanting to make some function async on top of a stack of 10 sync function calls, you're probably doing something ill-advised[1], and async/await making this difficult is valuable feedback on your design. Is this feedback useful in every system? No, just like static typing would be overkill for a five line shell script. But writing a paradigm off just because it makes it more difficult to set off certain footguns is, in my view, a mistake, and that's what irks me about this blog post. [1] keeping business logic separate from infra logic is typically a good idea, see hexagonal architecture, or imperative shell functional core. Async/await is a convenient mechanism to guarantee maintaining this architectural choice, even for developers less familiar with this architecture.