>
> See, what you call "paternalistic" I say is "basic good usability."
> Affordances are part of the design of everything.  Good design means
making doing the
>

If we worry about "intuitive usability", we should ban caching, finite
state machines, and of course, concurrency.
Parallelism? Not just ban it, but burn those who use it at the stake of the
inquisition! :)

In this context, the child-parent model has a flaw that directly
contradicts intuitive usage.
Let me remind you of the main rule:

Default behavior: All child coroutines are canceled if the parent is
canceled.

Now, imagine a case where we need to create a coroutine not tied to the
parent.
To do this, we have to define a separate function or syntax.

Such a coroutine is created to perform an action that must be completed,
even if the parent coroutines are not fully executed.
Typically, this is a critical action, like logging or sending a
notification.

This leads to an issue:

* Ordinary actions use a function that the programmer always remembers.
* Important actions require a separate function, which the programmer might
forget.

This is the dark side of any strict design when exceptions exist (and they
almost always do).

And the problem is bigger than it seems because:

1. The parent coroutine is created in Function A.
2. The child coroutine is created in Function B.
3. These functions are in different modules, written by different
developers.

Developer A implements a unique algorithm that cancels coroutine execution.
This algorithm is logical and correct in the context of A.
Developer B simply forgets that execution might be interrupted.
And boom! We've just introduced a bug that will send the entire dev team on
a wild goose chase.

This is why the Go model (without parent-child links) is different:
It makes chaining coroutines harder.
But if you don’t need chains, it’s simpler.
And whether you need chains or not is a separate question.

Possible scenarios in PHP *Scenario 1*

We need to generate a *report*, where data must be collected from multiple
services.

   - We create *one coroutine per service*.
   - Wait for all of them to finish.
   - Generate the report.

Parent-child model is ideal:
If the *parent coroutine* is canceled, the *child coroutines* are
meaningless as well.
------------------------------
*Scenario 2*

*Web server.* The API receives a request to create a *certificate*. The
algorithm:

   1. *Check* if we can do it, then create a *DB record* stating that the
   user has a certificate.
   2. *Send a Job* – notify other users who need to know about this event.
   3. *Return the certificate URL* (a link with an ID).

*Key requirement:*

   - *Heavy operations* (longer than *2-3 seconds*) should be performed *in
   a Job-Worker pool* to keep the server *responsive*.
   - Notifications are sent *as a separate Job* in *a separate coroutine*,
   which:
      - Can retry sending *twice if needed*.
      - Implements a *fallback mechanism*.
      - *Is NOT linked* to the request coroutine.

------------------------------
Which scenario is more likely for PHP?
>
> To quote someone on FP: "The benefit of functional programming is it
makes data flow explicit. The downside is it sometimes painfully explicit."
>
If there is a nesting of 10 functions where parameters are passed
explicitly, then the number of parameters in the top function will be equal
to the sum of the parameters of all other functions, and the overall code
coupling will be 100%. Parameters can be grouped into objects (structures),
thus reducing this problem. However, creating additional objects leads to
the temptation to shove a parameter into the first available object because
thinking about composition is a difficult task. This means that such an
approach either violates SOLID or increases design complexity. But usually,
the worst-case scenario happens: developers happily violate both SOLID and
design. :)

I think these principles are more suitable for areas where design planning
takes up 30-50% of the total development time and where such a time
distribution is rational in relation to the project's success.  At the same
time, the initial requirements change extremely rarely. PHP operates under
completely different conditions: "it was needed yesterday" :)

>
> As above, in simpler cases you can just make the context a boring old
function parameter,
>

What if a service wants to store specific data in the context?

As for directly passing the context into a function, the coroutine already
owns the context, and it can be retrieved from it. This is a consequence of
PHP having an abstraction that C/Rust lacks, allowing it to handle part of
the dirty work on behalf of the programmer. It's the same as when you use
$this when calling a method.

>
> Do you have a concrete example of where the inconvenience of explicit
context is sufficiently high to warrant an implicit global and all the
impacts that has?
>

The refactoring issue. There are five levels of nesting. At the fifth
level, someone called an asynchronous function and created a context.
Thirty days later, someone wanted to call an asynchronous function at the
first level of nesting. And suddenly, it turns out that the context needs
to be explicitly passed. And that's where the fun begins. :)

---
Ed.

Reply via email to