On 2 March 2018 at 19:05, Paul Moore <p.f.moore@gmail.com> wrote:
The problem with statement local variables is that the extent over
which the name is in scope is not as clear to the human reader (the
rules the *compiler* follows may be precise, but they aren't obvious
to the human reader - that's the root of the debate I'm having with
Chris over "what the reference implementation does isn't a sufficient
spec"). In particular, assignment statements are non-obvious, as shown
by the examples that triggered your suggestion of a "." prefix.

Those examples didn't trigger the suggestion: the suggestion was borne from the fact that I don't think it should be possible to close over statement locals.

If you can't close over statement locals, then it isn't acceptable to allow this scenario:

    x = 12
    if (True as x):
        def f():
            return x
        print(x, f()) # "True True"? Or "True 12"?
    print(x, f()) # "12 12", but it's not obvious why it isn't "True True"

By contrast, if the two kinds of local namespace are visibly different, then the nested scope *can't* give the appearance of referencing the statement local:

    x = 12
    if (True as .x):
        def f():
            return x
        print(.x, x, f()) # Clearly "True, 12, 12", since x never gets rebound
    print(x, f()) # Clearly "12, 12", since x never gets rebound


> Adding statement local variables into that mix *without* some form of
> syntactic marker would mean taking an already complicated system, and making
> it even harder to reason about correctly (especially if statement locals
> interact with nested scopes differently from the way other locals in the
> same scope do).

Well, an alternative to a syntactic marker would be an
easy-to-determine extent. That's where proposals like PEP 3150 (the
"given" clause) work better, because they provide a clearer indication
of the extent of the new scope. IMO, lack of a well-defined extent is
a flaw of this proposal, and syntactic markers are essentially a
(ugly) workaround for that flaw.

PEP 3150 ended up needing syntactic markers as well, to handle the forward references to names set in the `given` clause while staying within the LL(1) parsing design constraint imposed on Python's grammar.

Currently it proposes `?.name` as that marker, with `?` referring to the entire given namespace, but it could equally well use `.name` instead (and if you wanted a reference to a namespace instead, you'd need to define one inside the given clause).

One of the key *problems* with PEP 3150 though is that it doesn't compose nicely with other compound statements, whereas PEP 572 does (by treating each statement as its own extent - PEP 3150 then just provides a way to add a suite to statements that don't already have one of their own).
> Thus the intent behind the ".NAME" suggestion is to ask whether or not it's
> possible to allow for name bindings that are strictly local to a compilation
> unit (i.e. without allowing dynamic runtime access to outer scopes or from
> contained scopes), *without* incurring the cost of making ordinary NAME
> references even more complicated to understand.

... or the cost of imposing a more user-visible indication of the
extent of the scope into the proposal.

Right, but that extra notation *does* convey useful information to a reader that better enables local reasoning about a piece of code. Currently, if you're looking at an unfamiliar function and see a name you don't recognise, then you need to search the whole module for that name to see whether or not it's defined anywhere. Even if it's missing, you may still need to check for dynamic injection of module level names via globals().

Seeing ".name" would be different (both for the compiler and for the human reader): if such a reference can't be resolved explicitly within the scope of the current statement, then *it's a bug* (and the compiler would be able to flag it as such at compile time).


Nick Coghlan   |   ncoghlan@gmail.com   |   Brisbane, Australia