First, side note: When I said "Tim" in my earlier messages, I was in fact referring to Rowan. I do not know why I confused Tim and Rowan. My apologies to both Tim and Rowan for the confusion.
On Tue, Mar 18, 2025, at 2:26 AM, Edmond Dantes wrote:
Hello, Larry.
First off, it desperately needs an "executive summary" section up at the top.
There's a *lot* going on, and having a big-picture overview would help a ton. (For
examples, see property hooks[1] and pattern matching[2].)
I looked at the examples you provided, but I still don't understand
what exactly I could put in this section.
Key usage examples without explanation?
Do you think that would make the RFC better? I don’t really understand
how.
For a large RFC like this, it's helpful to get a surface-level "map" of the new feature first. What it is trying to solve, and how it solves it. Basically what would be the first few paragraphs of the documentation page, with an example or three. That way, a reader can get a sort of mental boundary of what's being discussed, and then can refer back to that when later sections go into all the nitty gritty details (which are still needed). As is, there's "new" syntax being introduced for the first time halfway through the document. I have a hard time mentally fitting that into the model that the previous paragraphs built in my head.
So as an outline, I would recommend:
* Statement of problem being solved
* Brief but complete overview of the new syntax being introduced, with minimal/basic explanation
* Theory / philosophy / background that people should know (eg, the top-down/bottom-up discussion)
* Detailed dive into the syntax and how it all fits together, and the edge cases
* Implementation related details (this should be the first place you mention the Scheduler that doesn't exist, or whatever)
Second, please include realistic examples. Nearly all of the examples are contrived,
Which examples do you consider contrived, and why?
The vast majority of the examples are "print Hello World" and "sleep()", in various combinations. That doesn't tell me how to use the syntax for more realistic examples. There's a place for trivial examples, definitely, but also for "so what would I do with it, really?" examples. It shows what you expect the "best practices" to be, that you're designing for.
The first non-foobar example includes a comment "of course you should
never do it like this", which makes the example rather useless
Do you mean working with a closure that captures a reference to `$this`?
But that has nothing to do with this RFC, either directly or
indirectly. And it’s not relevant to the purpose of the example.
The Coroutine Scope Lifetime example. It says after it:
"Note: This example contains a circular dependency between objects, which should be avoided in real-world development."
Which, as above, means it is not helpful in telling me what real-world development with this feature would look like. How *should* I address the use case in that example, if not with, well, that example? That's unclear.
And the second is
built around a code model that I would never, ever accept into a code base, so it's
again unhelpful.
Why?
The second code block under "Coroutine local context". It's all a series of functions that call each other to end up on a DB factory that uses a static variable, so nothing there is injectable or testable. It has the same "should be avoided in real-world development" problem, which means it doesn't tell me anything useful about when/why I'd want to use local context, whatever that is.
So would the functions/keywords be shortcuts for
some of the common functionality of a Scope object?
Were you confused by the fact that the `Scope` object has a `spawn`
method?
(Which is semantically close to the operator?)
I understand that having both a method and an operator can create
ambiguity, but it's quite surprising that it could be so confusing.
Yes, that, for example. It suggested in my mind that the `spawn` keyword would be an alias for currentScope()->spawn(). Whether that would be wise or not I don't know, but if that wasn't your intent, then it was confusing.
How should I have written about this? It's simply a part of reality as
it is. Why did this cause confusion?
Yes, I split this RFC into several parts because it's the only way to
decompose it.
It’s logical that this needs to be mentioned so that those who haven’t
followed the discussion can have the right understanding.
What’s wrong with that?
I am fully in favor of explicitly "linking" RFCs together, such that each sub-feature can be discussed separately. However, each RFC needs to, on its own, be self-contained and useful. Maybe less useful than if the whole set were passed, but at least useful on its own.
Sometimes that means the individual RFCs are still quite large (eg, property hooks, which was split from aviz). Other times they're quite small (eg, pipes, which is part one of like 4, all noted in Future Scope). But each RFC needs to "work" on its own.
Consider: Suppose this RFC passed, but the follow-up to add a Scheduler did not pass, for whatever reason. What does that leave us with? I think it means we have an approved RFC that cannot be implemented. That's not-good.
If this RFC adds a keyword "async" or "spawn" or whatever we end up with, then that keyword needs to be able to do something useful with just the functionality approved in this RFC. Some additional functionality may not be available until a later RFC, which makes the whole thing better, but it at least still does something useful with the approved RFC.
For example, I suspect all the context values bits could be punted to a future RFC. Yes, that means some things won't be possible in the 1.0 version of the feature; that's OK. It may mean designing the syntax in such a way that it will evolve cleanly to include it later. That's also OK. But whatever plumbing needs to exist for the user-facing functionality to work (eg, the scheduler) needs to be there at the same time the user-facing functionality is.
Are those
standard terms you're borrowing from elsewhere, or your own creation?
Unfortunately, I couldn't find terminology that describes these models.
However, the RFC itself provides definitions. What is confusing to you?
Mainly that I wasn't sure if this was drawing on existing literature and I should be googling for these terms or not. If there is no existing literature then defining your own terms is fine, just be clear that's what you're doing.
I cannot really tell which one the "playpen" model
would fit into.
If we're talking about the "nursery" model in Python, there is no
direct analogy because a *nursery is not a coroutine*, but rather a
*Scope* in this RFC.
Yes, I meant nursery. (I have a 6 month old baby; all these baby-related terms blend together in my head.
)
In this context, the model in the RFC is essentially no different from
nurseries in Python.
That was not at all evident to me from reading it.
The key difference lies elsewhere.
To define the structure, two elements are used:
• The coroutine itself
• An object of type *Nursery* or *Scope*
So in Python, coroutines work exactly the same way as in Go, and the
*nursery* is an additional mechanism to ensure structured concurrency.
In this RFC, there is a *nursery (Scope)*, but in addition to that,
*coroutines themselves are also part of the structure*.
(So this RFC uses a stricter approach than Python)
Does this mean it's not clear from the text?
Yes, because I didn't get that message from it. (This is where a 10,000 foot overview early on would be helpful.)
I honestly cannot see a use case at this point for starting coroutines in arbitrary scopes.
If we're talking about launching a coroutine in *GlobalScope*, then
it's 99% likely to be an *anti-pattern*, and it might be worth removing
entirely. It's the same as creating a global variable using `$GLOBAL`.
However, if we're referring to a *pattern where a service defines its
own `$scope`*, then this is probably one of the most useful aspects of
this RFC.
This is where more practical examples would be helpful. Eg, when and why would a service define its own scope, and what are the implications?
Elsewhere in the thread, Tim noted that
we should unify the function call vs closure question.
It would be great if this were possible, but so far, I haven't found a
syntax that satisfies both requirements.
Of course, the syntax could be unified like this:
spawn function($params): string use() {
}('param');
and
function test($x);
spawn test($x);
But it looks unnatural...
Right now, I'm mentally close to the approach that Rowan_Tommins also
described.:
spawn use($parameters): string {};
spawn test($x);
Let me ask this: With the spawn/start/whatever keyword, what is the expected return value? Does it block until that's done? Do I get back a future?
If the mental model is "take a function call like you already had and stick a 'start new coroutine keyword on it'", then that leads to one set of assumptions and therefore syntax behavior. If it's more like "fork a subprocess that I can communicate with later", that leads to a different set of assumptions.
On Tue, Mar 18, 2025, at 2:59 AM, Rowan Tommins [IMSoP] wrote:
I actually think what you're describing is very similar to the RFC,
just with different syntax; but your examples are different, so you're
talking past each other a bit.
That is quite possible. Given the other comments above, I'd say likely.
The "request handler" use case could easily benefit from a
"pseudo-global" scope for each request - i e. "tie this to the current
request, but not to anything else that's started a scope in between".
Potentially, though I would still question why it cannot just be explicitly passed values. (All data flow is explicit; sometimes it's painfully explicit.
)
--Larry Garfield