Re: PHP True Async RFC Stage 4
| From: | John Bafford | Date: | Thu, 23 Oct 2025 16:40:37 +0000 |
| Subject: | Re: PHP True Async RFC Stage 4 | ||
| References: | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-128935@lists.php.net to get a copy of this message | ||
> On Oct 23, 2025, at 04:18, Rob Landers <rob@bottled.codes> wrote:
>
> On Thu, Oct 23, 2025, at 09:59, Edmond Dantes wrote:
>>
>>
>> I’d like you to have the full information necessary to make an
>> informed decision.
>>
>> There are two or more channel objects that need to be awaited. How
>> should this be implemented?
>>
>> Currently
>> ```php
>> [$result1, $result2] = awaitAny($channel1, $channel2);
>> ```
>>
> As for which one I'd rather have, I'd rather have the one where my examples hold
> true:
>
> assert(awaitAll($a, $b, $c) === [await($a), await($b), await($c)]);
>
> It's clear what it does and that it is just a shorthand. When that fails, we have issues.
>
> — Rob
Just to add to this, in Swift, a Task (the analogue to this proposal's Coroutine) can emit,
exactly once, a value or an error (exception). Attempting to read its value (or error) multiple
times gets you the same value back. This is very handy in memoization patterns.
In this example, multiple readers can call getImage() with the same id and get a value, regardless
of whether they're the first (which kicks off image loading), whether the image loading has
completed, or whether the image load is still in progress. (Technically, one could omit the
LoadingBox wrapper entirely, but that would cause the Task to persist after it's completed, and
that's a more expensive structure than a simple enum.)
```swift
private enum LoadingBox<T : Sendable> {
case inProgress(Task<T, Never>) //Task<Success, Failure>
case ready(T)
var value: T {
get async {
switch self {
case .inProgress(let task):
return await task.value
case .ready(let value):
return value
}
}
}
}
private var imageCache: [ImageId : LoadingBox<Image>] = [:]
func getImage(_ id: ImageId) async -> Image {
if let cache = imageCache[id] {
return await cache.value
}
let task = Task { ...load the image... }
imageCache[id] = task
return await task.value
}
```
Ultimately, I would want to be able to write code such as this in PHP (with the generics, of course,
but I'm not holding my breath).
If coroutines sometimes return single values and sometimes return multiple values, it becomes much
harder to write a generic async cache/memoization system, because you can't indicate via the
type system that a single value is expected.
-John