5 ms·
Languages with effect systems typically let callers inherit the effects of their callees (e.g., calling an async function means the caller also is async), or fo
by simonask 5d ago
Languages with effect systems typically let callers inherit the effects of their callees (e.g., calling an async function means the caller also is async), or force them to handle the effect (e.g., spawn the async call as a task and waiting synchronously for it to finish).
Effects are just a generalization, where async/await is one particular effect.
But: The fact that an operation now does some kind of I/O, or waits for user input, or whatever else you might express using async, has an _enormous_ impact on the architecture of your program. The “virality” of async is completely a feature, because it forces you to actually deal with that change, resulting in much more robust software.
It’s “inconvenient” because the architecture of your program changed. That’s what the job is, though. Languages that don’t help you here (by hiding that you made a change with huge ramifications) make it actively harder to deliver working software, in my opinion. You get there faster, but it won’t keep working.
- mitxela 5d agoThe problem with effects systems is that effects aren't generalisable. Every effect is unique. Sometimes they can be applied automatically and sometimes not. You can have the compiler automatically recompile map with async to make map<async>, likewise map<pure> and map<nofail> and map<noblock> but they will not be optimal; map<async> could be parallel but isn't. And you probably want to control the amount of parallelism at each call site, which just makes it a completely different function. It's likely that you wrote map in a way that uses a loop counter and it's possible the compiler can't prove it's pure. map<abortable> is likely correct, but the compiler has absolutely no way to prove that, and other functions won't be correct if you naively make them possible to abort from outside.
- mitxela 5d agoIt's a fundamental architecture change only if we assume async == slow (or potentially slow) which isn't always true. Otherwise you might want to run something inline that the language designer made async - such as writing a file in /tmp.
- simonask 4d agoObviously, "slow" has very different meanings depending on context. Allocating a task object or interacting with the runtime in any way may be quite slow when you're in a tight inner loop, but it may be perfectly fast in the context of handling an HTTP request. Async is almost never free of charge. The only language that can get close to zero cost is Rust, but even it needs heap allocations for async recursion.