I was reading today about a proposal regarding the Rust language’s use of async and await. which are related to colored subprograms. If you’re familiar with them, read on; if not, click the blurred text at bottom for a hopefully-correct explanation.
I naturally wondered how this issue affects Ada. I’m not as familiar with task types as I should be, even though I used them a few years ago, and Ada 2022 brings the new parallel keyword, which I have yet to use. After refreshing my memory:
Is my impression correct that Ada’s approach to concurrency and parallelism circumvents “colored” subprograms altogether? After all, task entries are called just like regular subprograms, and subprograms with parallel blocks are called just like other subprograms.
My apologies if I’m being unclear; please don’t hesitate to correct or scold me if I’m babbling nonsense. I know what I’m trying to say, but I’m not used to talking about these particular issues.
I think this page is the origin of the term, and hopefully I can summarize the issue correctly as: subprograms have to be called differently depending on their “color”: async functions have to be called with a trailing .await, which means they can’t be used in places where non-async functions can. The use of await somewhere “taints” a subprogram (I don’t quite get why) so that every subprogram that wants to invoke it must also be declared async, chaining the requirements of async’s and .await’s to an unpleasant level. I’ve felt it most painfully when I want to use an async function in an iterator: no can do.
async and await can be implemented using Ada tasks, just as Rust and other languages implement them using tasking primitives; an async subprogram would call an entry in the task that starts a computation, and await would call an entry that receives the results. There’s more to it; the code that calls the async subprogram has to arrange to use the result somehow.
This is similar to “futures” implemented by other languages.
All such constructs are an attempt to make tasking easier to use. I prefer to be more explicit about what a task is, so I like the Ada model better. It might be interesting to see an AI (Ada Rapporteur Group - Ada Interpretations) for this, if someone has an application where it is clearly better/simpler than Ada tasks.
Thank you for the reply. I guess my question was unclear, no thanks to the headline:
Do colored subprograms exist in Ada?
That makes it seem I was concerned with whether async and .awaitwere implented in Ada, and if not, could they be.
I was actually more interested in the question stated in the body:
[Does] Ada’s approach to concurrency and parallelism [circumvent] “colored” subprograms altogether?
That is, are the task and parallel “colored” the way async and .await are, tainting any subprogram that uses them into being called in a special fashion?
It seems, based both on what I’ve been reading and on your answer, and on a review of how I used task 5 years ago, that the answer to the question I meant to ask is, No, and the answer to the question I actually asked is, You can implement them.
If that’s incorrect, I’d appreciate correction, but otherwise I appreciate the clarification.
To be a little bit theoretical about this, the reason async/await ends up coloring functions is because asynchronicity is a kind of “effect”, similar to exceptions: if some function foo raises an exception, then any other function bar which calls foo essentially also “raises” that exception, unless they explicitly catch it. Similarly, if some function foo pauses execution by awaiting a future, then any function bar which calls foo effectively also pauses execution.
For various reasons (backwards compatibility, compiler transformations), some languages like to explicitly mark which functions may end up pausing execution like this, which is where the whole function coloring issue comes from. Imagine if you had to mark every function that might raise an exception with excepts - you’d run into the exact same issue. An excepts function can call any function, but a non-excepts function cannot call one marked excepts.
Actually, that last part isn’t quite true since you can actually catch exceptions, and therefore stop them from bubbling up. Most languages with async often do let you “catch” the asynchronicity - most Rust executors like Tokio give you a function block_on which lets you block on the result of a future - but it’s often a bit inconvenient.
AFAIK, Ada does not have any way of “marking” a function like with async or excepts, and so it doesn’t have this issue where you’re limited in what functions you can call. Still, it has exceptions, and so in a way, you still have function colors - they’re just not tracked by the compiler.
Anyways, one of the big reasons I really like Ada’s tasks and the new parallel features is that they are examples of structured concurrency, which means that a function won’t return until all of the tasks it spawned have finished. It’s a bit like if you put block_on around the body of every async function, and it makes for a very predictable and composable way of multitasking.
Don’t know about others, but I’d think that for a subprogram to create even one task would be very unusual indeed. Of course, I’m used to the Ravenscar profile, where tasks have to be created at library level.
That’s fair. Spawning tasks inside subprograms is something that should be done with a bit of care since the overhead can sometimes offset any gains, and the use of entries can easily create hard-to-debug deadlocks. Still, there are some situations where I’ve found it to be a useful tool, such as reading multiple large files at once. That’s a situation where you’d typically reach for async/await in another language, hitting the function coloring problem. Having tasks inside subprograms let’s you neatly encapsulate that.
That said, I’m not a terribly experienced Ada programmer, so it’s quite possible it is an anti-pattern.