> 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.

> 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.

> 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.

> 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.

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.

> 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.

> 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.

Reply via email to