On Wed, Oct 22, 2025, at 14:57, Edmond Dantes wrote:
> >
> > If you await a value, everything works, but then someone somewhere else 
> > awaits the same Awaitable that wasn't actually a "one-shot" Awaitable,
> > so now everything breaks sometimes, and other times not -- depending on 
> > which one awaits first.
> >
> 
> There is a function:
> 
> ```php
> function my(mixed $i)
> {
>     return $x + 5;
> }
> ```
> 
> Someone called it like this somewhere: my("string"), and the code broke.
> The question is: what should be done?
> 
> Do we really have to get rid of the mixed type just to prevent someone
> from breaking something?
> If a programmer explicitly violates the contracts in the code and then
> blames the programming language for it. So the programmer is the one
> at fault.
> If someone uses a contract that promises non-idempotent behavior, it’s
> logical to rely on that.

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.

> > 2. memoization becomes an issue
> 
> The same question: why should the code work correctly if the
> agreements are violated at the contract level?
> There’s no element of randomness here.
> 1. A person deliberately found the Awaitable interface and inserted it
> into the code.
> 2. They deliberately wrote that function.
> 
> So what exactly is the problem with the interface itself?
> 
> > With a "multi-shot" Awaitable, this is not practical or even a good idea. 
> > You can't write general-purpose helpers, at all.
> In what way are generalized types a problem?
> Why not just use the contracts correctly?
> 
> ```php
> function getOnce(Awaitable $some) {
> 
>   if($some instanceof FutureLike === false) {
>      return await($some);
>   }
> 
>   static $cache = [];
>   $id = spl_object_id($some);
>   return $cache[$id] ??= await($some);
> }
> ```

The "FutureLike" type is exactly what I'm arguing for!

> > 3. static analysis
> I really don’t see how static analysis could help here. What exactly
> would it be checking?

I can imagine getting something like the following

foreach($next = await($awaitable)) { }
*error*: usage of single-shot Awaitable in loop, this is an infinite loop

or:

$val = await($awaitable);
//in another file/function/5 lines later/etc
$val = await($awaitable);
*error*: multiple usages of multi-shot Awaitable; you may get a different 
result on each invocation of await()

or something like that.

> 
> > 4. violation of algebraic laws with awaitAll/awaitAny
> 
> And the fact that awaitAll/awaitAny are designed to correctly handle
> Awaitable objects?
> I’m not aware of any such violations.

I had to go take a look at the implementation. It appears NOT to handle this 
case, at all. Meaning it treats all Awaitables passed to it as a single-shot 
Awaitable. (the code was easy to follow, but maybe I missed something). The RFC 
doesn't spell this out and needs some work here.

When I say "violations" I mean that, assuming $a and $b resolve instantly:

awaitAll([$a, $b]) !== [await($a), await($b)]
awaitAny([delay(10, fn() => await($a), await($b)]) !== await($b)

> 
> > 5. violation of own invariants in the RFC
> Can you show which statement of the RFC is being violated here?
> What invariants?

sidenote: It would really help to include the full context of the emails you're 
replying to so I don't have to open our email conversation in another window to 
see exactly what I wrote.

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.

> 
> > 6. common patterns aren't guaranteed anymore
> 
> So JavaScript yes?
> 
> ```js
> class NonIdempotentThenable {
>   constructor() {
>     this.count = 0;
>   }
>   then(resolve, reject) {
>     this.count++;
>     resolve(this.count);
>   }
> }
> 
> async function demo() {
>   const obj = new NonIdempotentThenable();
>   console.log(await obj); // 1
>   console.log(await obj); // 2
>   console.log(await obj); // 3
> 
>   if (await obj) {
>     console.log(await obj); // 5
>   }
> }
> 
> demo();
> ````
> 
> Other cases generally have the same problem — a violation of the agreement.

But you're talking about this being *the default*. There's a reason this is an 
anti-pattern in Javascript and violation of the 'agreement' there.

>> how will the scheduler handle backpressure?
> 
> I don’t really understand the problem with the Scheduler, and even
> less its relation to backpressure.
> Dealing with backpressure is a matter of queue implementation.
> The Awaitable contract has nothing to do with this situation.

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!

>> I'm not sure what your thought process is here, because in the last few 
>> emails you've gone from "maybe"
>> to doubling-down on this (from my perspective), but I feel like this will be 
>> a footgun to both developers and the future of the language.
> 
> Confident about what exactly?
> And what exactly would be the footgun?
> These are very general statements.
> 
> It seems to me that people in this conference use the word “footgun”
> far too often — and not always in the right context.
> There’s a lack of rational boundaries here.
> Not every programmer’s mistake is a footgun.
> Especially that:
> 
> ```php
> if (await($response)) {
>   return await($response);
> }
> ```

A footgun is any reasonable looking code that subtly breaks because the 
contract is weak, not because the programmer didn't RTFM.

— Rob

Reply via email to