Skip Menu |

This queue is for tickets about the Future-AsyncAwait CPAN distribution.

Report information
The Basics
Id: 122793
Status: new
Priority: 0/
Queue: Future-AsyncAwait

People
Owner: Nobody in particular
Requestors: leonerd-cpan [...] leonerd.org.uk
Cc:
AdminCc:

Bug Information
Severity: Wishlist
Broken in: (no value)
Fixed in: (no value)



Subject: Consider the semantics of `local` inside `await`
Without the async/await syntax or similar behaviours, the semantics of `local` declarations in are clearly defined. `local` establishes a region of modification of a variable that is temporary in both dynamic and temporal scope, because without deferred continuations, those are the same thing. However, once we have the ability to suspend and resume a function later on, we run into a mismatch of semantics due to the fact that a single dynamic scope can now be split across multiple disjoint temporal regions. To take a simple example: our $DEBUG; sub act { say STDERR "I'm now acting" if $DEBUG } async sub foo { local $DEBUG = 1; act(); await $f; } Semantically, it should be pretty clear here that the call to `act()` is intended to cause a debugging print to occur. The dynamic extent of the call to `act()` occurs within the dynamic extent of the `local $DEBUG` assignment, and that matches the temporal extent, so all is happy. Next, consider: async sub bar { local $DEBUG = 1; await $f; act(); } Here, if the `await` expression has to suspend and resume on `$f` then the dynamic and temporal extents are now different. If we decide that `local` should have dynamic (rather than temporal) extent, then we can arrange for its value to be saved and restored around the `await` operation, effectively performing: async sub bar { local $DEBUG = 1; # save $DEBUG here await $f; # restore $DEBUG here act(); } This technique however does not extend beyond `await` expressions in other functions called by this one. Consider now: async sub inner { act(); #1 await $f; act(); #2 } async sub baz { local $DEBUG = 1; await inner(); act(); #3 } It is clear here that the nested call to `act()` labelled #1 will see the intended value of `$DEBUG` because its temporal vs. dynamic extents have not become disconnected due to an intervening `await` expression. If we perform the localisation save/restore logic as before, then the call labelled #3 will also see it. However, the call labelled #2 will not, because it is nested inside a dynamic scope whose `await` expression did not realise to save and restore the value of `$DEBUG`, as it wasn't in the savestack for the scope of its own function. -- Paul Evans
That all said, there is at least one special case where the utility of `local` outweighs the questions on its semantics - `local $@`. It's common to `eval{}`-based exception handling to localise the value of $@ (for example, Syntax::Keyword::Try even does it implicitly internally). In the special case of $@, the general semantic ambiguity of what localising it means doesn't really apply, because in practice it is only read from directly after a failed `eval{}` block, so if the eval has returned it has updated the correct value into there, so the localisation of it does not matter. We therefore should look at implementing this as a special case. It may be that there are other special-cases we can safely implement too. -- Paul Evans