On 6 March 2025 19:07:34 GMT, Larry Garfield <la...@garfieldtech.com> 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]