Good day, Larry. > First off, as others have said, thank you for a thorough and detailed proposal. Thanks!
> * A series of free-standing functions. > * That only work if the scheduler is active. > * The scheduler being active is a run-once global flag. > * So code that uses those functions is only useful based on a global state not present in that function. > * And a host of other seemingly low-level objects that have a myriad of methods on them that do, um, stuff. > * Oh, and a lot of static methods, too, instead of free-standing functions. Suppose these shortcomings don’t exist, and we have implemented the boldest scenario imaginable. We introduce Structured Concurrency, remove low-level elements, and possibly even get rid of Future. Of course, there are no functions like startScheduler or anything like that. 1. In this case, how should PHP handle Fiber and all the behavior associated with it? Should Fiber be declared deprecated and removed from the language? What should the flow be? 2. What should be done with I/O functions? Should they remain blocking, with a separate API provided as an extension? 3. Would it be possible to convince the maintainers of XDEBUG and other extensions to rewrite their code to support the new model? ( *If you're reading this question now, please share your opinion.* ) 4. If transparent concurrency is introduced for I/O in point 2, what should be done with Revolt + AMPHP? This would break their code. Should an additional function or option be introduced to switch PHP into "legacy mode"? I share your feelings on many points, but I would like to see some real-world alternative. > > I commend to your attention this post about a Python async library > Structured concurrency is a great thing. However, I’d like to avoid changing the language syntax and make something closer to Go’s semantics. I’ll think about it and add this idea to my TODO. > async $context { > // $context is an object of AsyncContext, and can be passed around as such. > // It is the *only* way to span anything async, or interact with the async controls. > // If a function doesn't take an AsyncContext param, it cannot control async. This is good. This is a very elegant solution. Theoretically. However, in practice, if you require explicitly passing the context to all functions, it leads to the following consequences: 1. The semantics of all functions increase by one additional parameter (*Signature bloat*). 2. If an asynchronous call needs to be added to a function, and other functions depend on it, then the semantics of all dependent functions must be changed as well. In strict languages, a hybrid model is often used, or like in Go, where the context is passed explicitly as a synchronization object, but only when necessary. In this example, there is another aspect: the fact that async execution is explicitly limited to a specific scope. This is essentially the same as startScheduler, and it is one of the options I was considering. Of course, startScheduler can be replaced with a construction like async(function() { ... }). This means that async execution is only active within the closure, and coroutines can only be created inside that closure. This is one of the semantic solutions that allows removing startScheduler, but at the implementation level, it is exactly the same. What do you think about this? > I'm not convinced that sticking arbitrary key/value pairs into the Context object is wise; Why not? > that's global state by another name Static variables inside a function are also global state. Are you against static variables? > But if we must, the above would handle all the inheritance and override stuff quite naturally. Possibly with: How will a context with open string keys help preserve service data that the service doesn't want to expose to anyone? The Key() solution is essentially the same as Symbol in JS, which is used for the same purpose. Of course, we could add a coroutine static $var construct to the language syntax. But it's all the same just syntactic sugar that would require more code to support. > [$in, $out] = Channel::create($buffer_size); This semantics require the programmer to remember that two variables actually point to the same object. If a function has multiple channels, this makes the code quite verbose. Additionally, such channels are inconvenient to store in lists because their structure becomes more complex. I would suggest a slightly different solution: <code php> $in = new Channel()->getProducer(); async myFunction($in->getConsumer()); <code> This semantics do not restrict the programmer in usage patterns while still allowing interaction with the channel through a well-defined contract. Thanks for the great examples, and a special thanks for the article. I also like the definition of context. Ed