[Python-ideas] A "local" pseudo-function
Soni L.
fakedme+py at gmail.com
Sat Apr 28 10:21:09 EDT 2018
On 2018-04-27 11:37 PM, Tim Peters wrote:
> A brain dump, inspired by various use cases that came up during the
> binding expression discussions.
>
> Idea: introduce a "local" pseudo-function to capture the idea of
> initialized names with limited scope.
>
> As an expression, it's
>
> "local" "(" arguments ")"
>
> - Because it "looks like" a function call, nobody will expect the targets
> of named arguments to be fancier than plain names.
>
> - `a=12` in the "argument" list will (& helpfully so) mean pretty much the
> same as "a=12" in a "def" statement.
>
> - In a "local call" on its own, the scope of a named argument begins at the
> start of the next (if any) argument, and ends at the closing ")". For the
> duration, any variable of the same name in an enclosing scope is shadowed.
>
> - The parentheses allow for extending over multiple lines without needing
> to teach editors (etc) any new tricks (they already know how to format
> function calls with arglists spilling over multiple lines).
>
> - The _value_ of a local "call" is the value of its last "argument". In
> part, this is a way to sneak in C's comma operator without adding cryptic
> new line noise syntax.
>
> Time for an example. First a useless one:
>
> a = 1
> b = 2
> c = local(a=3) * local(b=4)
>
> Then `c` is 12, but `a` is still 1 and `b` is still 2. Same thing in the end:
>
> c = local(a=3, b=4, a*b)
>
> And just to be obscure, also the same:
>
> c = local(a=3, b=local(a=2, a*a), a*b)
>
> There the inner `a=2` temporarily shadows the outer `a=3` just long
> enough to compute `a*a` (4).
>
> This is one that little else really handled nicely:
>
> r1, r2 = local(D = b**2 - 4*a*c,
> sqrtD = math.sqrt(D),
> twoa = 2*a,
> ((-b + sqrtD)/twoa, (-b - sqrtD)/twoa))
>
> Everyone's favorite:
>
> if local(m = re.match(regexp, line)):
> print(m.group(0))
>
> Here's where it's truly essential that the compiler know everything
> about "local", because in _that_ context it's required that the new
> scope extend through the end of the entire block construct (exactly
> what that means TBD - certainly through the end of the `if` block, but
> possibly also through the end of its associated (if any) `elif` and
> `else` blocks - and similarly for while/else constructs).
>
> Of course that example could also be written as:
>
> if local(m = re.match(regexp, line), m):
> print(m.group(0))
>
> or more specifically:
>
> if local(m = re.match(regexp, line), m is not None):
> print(m.group(0))
>
> or even:
>
> if local(m = re.match(regexp, line)) is not None:
> print(m.group(0))
>
> A listcomp example, building the squares of integers from an iterable
> but only when the square is a multiple of 18:
>
> squares18 = [i2 for i in iterable if local(i2=i*i) % 18 == 0]
>
> That's a bit mind-bending, but becomes clear if you picture the
> kinda-equivalent nest:
>
> for i in iterable:
> if local(i2=i*i) % 18 == 0:
> append i2 to the output list
>
> That should also make clear that if `iterable` or `i` had been named
> `i2` instead, no problem. The `i2` created by `local()` is in a
> wholly enclosed scope.
>
> Drawbacks: since this is just a brain dump, absolutely none ;-)
>
> Q: Some of those would be clearer if it were the more Haskell-like
>
> local(...) "in" expression
>
> A: Yup, but for some of the others needing to add "in m" would be
> annoyingly redundant noise. Making an "in" clause optional doesn't
> really fly either, because then
>
> local(a='z') in 'xyz'
>
> would be ambiguous. Is it meant to return `'xyz'`, or evaluate `'z'
> in 'xyz'`? And any connector other than "in" would make the loose
> resemblance to Haskell purely imaginary ;-)
>
> Q: Didn't you drone on about how assignment expressions with complex
> targets seemed essentially useless without also introducing a "comma
> operator" - and now you're sneaking the latter in but _still_ avoiding
> complex targets?!
>
> A. Yes, and yes :-) The syntactic complexity of the fully general
> assignment statement is just too crushing to _sanely_ shoehorn into
> any "expression-like" context.
>
> Q: What's the value of this? local(a=7, local(a=a+1, a*2))
>
> A: 16. Obviously.
>
> Q: Wow - that _is_ obvious! OK, what about this, where there is no
> `a` in any enclosing scope: local(a)
>
> A: I think it should raise NameError, just like a function call would.
> There is no _intent_ here to allow merely declaring a local variable
> without supplying an initial value.
>
> Q: What about local(2, 4, 5)?
>
> A: It should return 5, and introduce no names. I don't see a point to
> trying to outlaw stupidity ;-) Then again, it would be consistent
> with the _intent_ to require that all but the last "argument" be of
> the `name=expression` form.
>
> Q: Isn't changing the meaning of scope depending on context waaaay magical?
>
> A: Yup! But in a language with such a strong distinction between
> statements and expressions, without a bit of deep magic there's no
> single syntax I can dream up that could work well for both that didn't
> require _some_ deep magic. The gimmick here is something I expect
> will be surprising the first time it's seen, less so the second, and
> then you're never confused about it again.
>
> Q: Are you trying to kill PEP 572?
>
> A: Nope! But since this largely subsumes the functionality of binding
> expressions, I did want to put this out there before 572's fate is
> history. Binding expressions are certainly easier to implement, and I
> like them just fine :-)
>
>
> Note: the thing I'm most interested in isn't debates, but in whether
> this would be of real use in real code.
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
Why not non-lexical variables?
Basically, make this work (and print 3):
def test():
i = 3
def test_inner():
print(i)
hide i
i = 4
test_inner() # 3
print(i) # 4
`hide`, unlike `del`, only applies to the current scope, and only
forward. it does what it says on the tin: makes the variable
disappear/be hidden.
ofc, python doesn't support shadowing of variables, so this is kinda
useless, but eh I thought it was a cool idea anyway :/
(See also this thread on the Lua mailing list:
https://marc.info/?l=lua-l&m=152149915527486&w=2 )
More information about the Python-ideas
mailing list