On Wed, Oct 22, 2025, at 17:30, Edmond Dantes wrote:
> > This isn't even the same example. We're not talking about type juggling, 
> > but an interface. Mixed is not an object nor an interface.
> Why?
> Type and Interface are contracts.
> 
> > The "FutureLike" type is exactly what I'm arguing for!
> 
> I have nothing against this interface. My point is different:
> 1. Should the await and awaitXX functions accept only Future?
> 2. Should Awaitable be hidden from the PHP userland?
> 
> > foreach($next = await($awaitable)) { }
> What’s the problem here? The object will return a result.
> If the result is iterable, it will go into the foreach loop. An
> absolutely normal situation.

Assuming an awaitable is idempotent (such as the result of a coroutine), this 
is an infinite loop. There’s a 99.9% chance that’s unintended, which is part of 
the point of static analysis.

> > error: multiple usages of multi-shot Awaitable; you may get a different 
> > result on each invocation of await()
> Why can’t you call await twice? What’s illegal about it?
> If it’s a Future, you’ll get the previous result; if it’s an
> Awaitable, you’ll get the second value.
> But the correctness of the code here 100% depends on the programmer’s intent.

There’s no world this would be intentional to get two separate values in two 
separate places, especially concurrently, except in very rare circumstances. 
You can’t guarantee ordering, and I can’t imagine wanting to interleave results 
across multiple code paths. Well, I can, but that feels like a very brittle 
system and a pita to maintain/debug.

> 
> > The RFC doesn't spell this out and needs some work here.
> I don’t understand what exactly isn’t specified in the RFC?
> 
> > When I say "violations" I mean that, assuming $a and $b resolve instantly:
> > awaitAll([$a, $b]) !== [await($a), await($b)]
> > ...
> 
> This is a logical error. Сircular reasoning. The desired behavior is
> being used here as proof of the undesired one. (recursion)
> In the general case, these equations should not work — and that’s
> normal if the code expects entities that are not Future, but, for
> example, channels.

Ok. If it’s circular reasoning, then what’s the definition of awaitAll? How do 
we explain it to junior devs, what guarantees can we make from it?

> > This is my mistake though. The discussion about await() is all mixed up 
> > with coroutines so its hard to tell what the actual behaviour for await() 
> > is outside of the context of coroutines.
> > The violation I thought of only applies to coroutines, the actual behaviour 
> > is "undefined" in the RFC.
> 
> `await()` does two things:
> 1. Puts the coroutine into a waiting state.
> 2. Wakes it up when the `Awaitable` object emits an event.
> 
> `awaitAny` / `awaitAll` do the same but for a list of objects or an iterator.

The RFC says that for *coroutines, *but not *Awaitable.* It is undefined.

> 
> However, the result of `await()` depends on the object being awaited.
> Thus, there are two separate contracts here:
> 1. **The waiting contract** — how the waiting occurs.
> 2. **The result contract** — how the result is obtained.
> 
> From a language design perspective, there should be an explicit
> representation for these contracts.
> Something similar appeared only in Swift (e.g., `try await`).
> 
> So, from the standpoint of “perfect design,” this is a simplification,
> which makes it not ideal.
> 
> I can remove the `Awaitable` interface from the `RFC` and replace it
> with `FutureLike`. It costs nothing to do so.
> But before doing that, I want to be at least 95% sure that in 2–3
> years we won’t have to bring it back or add workarounds.

Bringing it back is much much much easier than taking it away.

> 
> > It has everything to do with it! If a multi-shot Awaitable keeps emitting, 
> > this causes repeated wakeups to the same continuation. It has to schedule 
> > this every time and there isn't a way to say "don't > produce until 
> > consumed". You can basically starve other tasks from executing. However, if 
> > you use iterables of single-shot Awaitables, you get backpressure "for 
> > free" and don't request the next
> > future until the previous one is complete, otherwise, the buffering 
> > pressure lands in the awaitable (unbounded memory) or in the schedulers 
> > ready queue (which affects fairness). Further, when
> > cancelling a multi-shot awaitable, should the scheduler drop pending 
> > emissions and what happens if it keeps re-enqueing it anyway? It makes the 
> > scheduler far more complicated than it needs to > be!
> 
> If an Awaitable object isn’t being awaited, it can’t emit events, nor
> can it interfere with the scheduler.
> Unless the programmer explicitly creates new coroutines for every event by 
> hand.
> If an Awaitable has a lot of data and wakes up 1000 coroutines, then
> the scheduler will ensure that all those coroutines get executed.
> But until these 1000 coroutines finish processing the data, the
> Awaitable cannot wake anyone else.
> So there is a natural limit to the number of subscribers. it can only
> be exceeded through explicitly incorrect handling of coroutines.
> 
> Because the scheduler’s algorithm is extremely simple, it has little
> to no starvation issues.
> Starvation problems usually arise in schedulers that use more
> “intelligent” approaches.

You’re making some assumptions:
 1. That the scheduler will always be cooperative.
 2. That the scheduler you implemented as a proof-of-concept will be the one 
shipped.
There’ll come a day where someone will want to write a preemptive coroutine 
scheduler, or even a multi-threaded scheduler. That might be in 20–30 years, or 
next year; we don’t know. But if they do, having Awaitables multi-shot *will* 
prevent that from being a realistic PR. The amount of complexity required for 
such an implementation would be huge.

> 
> > A footgun is any reasonable looking code that subtly breaks because the 
> > contract is weak, not because the programmer didn't RTFM.
> The code cannot be considered “reasonable,” because in this case the
> programmer is clearly violating the contract.
> And not because the contract is complex or confusing, but because they
> completely ignored it. This doesn’t look like a footgun.
> 

At this point, we’re debating philosophy, but according to the RFC, coroutines 
will be entirely safe to handle this way, but not ALL awaitables. *That’s what 
makes it a footgun.* Sometimes, it’s reasonable, and it works ... and in 
certain cases, it doesn’t work at all. Nobody can tell by simply reading the 
code in a code review unless they had been shot in the foot before.

— Rob

Reply via email to