On 6 March 2025 19:07:34 GMT, Larry Garfield <[email protected]> wrote:
>It is literally the same argument for "pass the DB connection into the constructor,
>don't call a static method to get it" or "pass in the current user object to the
>method, don't call a global function to get it." These are decades-old discussions with
>known solved problems, which all boil down to "pass things explicitly."
I think the counterargument to this is that you wouldn't inject a service that implemented a
while loop, or if statement. I'm not even sure what mocking a control flow primitive would
mean.
Similarly, we don't pass around objects representing the "try context" so that we can
call "throw"as a method on them. I'm not aware of anybody complaining that they
can't mock the throw statement as a consequence, or wanting to work with multiple "try
contexts" at once and choose which one to throw into.
A lexically scoped async{} statement feels like it could work similarly: the language primitive for
"run this code in a new fiber" (and I think it should be a primitive, not a function or
method) would look up the stack for an open async{} block, and that would be the "nursery"
of the new fiber. [You may not like that name, but it's a lot less ambiguous than
"context", which is being used for at least two different things in this discussion.]
Arguably this is even needed to be "correct by construction" - if the user can pass around
nurseries, they can create a child fiber that outlives its parent, or extend the lifetime of one
nursery by storing a reference to it in a fiber owned by a different nursery. If all they can do is
spawn a fiber in the currently active nursery, the child's lifetime guaranteed to be no longer
than its parent, and that lifetime is defined rigidly in the source code.
Rowan Tommins
[IMSoP]