[Python-ideas] If branch merging

Nick Coghlan ncoghlan at gmail.com
Mon Jun 8 14:26:28 CEST 2015


On 8 June 2015 at 21:24, Andrew Barnert <abarnert at yahoo.com> wrote:
>
> Now you really _are_ reinventing let. A let expression like this:
>
>     x = let b=a.b in (b if b else a.c)
>
> ... is effectively just syntactic sugar for the lambda above.

Sure, I've thought a *lot* about adding let-type syntax - hence PEP's
403 (@in) and 3150 (given) for a couple of variations on statement
level local variables.

The problem with a let expression is that you still end up having to
jumble up the order of things, just as you do with the trick of
defining and calling a function, rather than being able to just name
the subexpression on first execution and refer back to it by name
later rather than repeating the calculation.

Thus a let expression doesn't actually help all that much with
improving the flow of reading or writing code - you still have the
step of pulling the subexpression out and declaring both its name and
value first, before proceeding on with the value of the calculation.
That's not only annoying when writing, but also increases the
cognitive load when reading, since the subexpressions are introduced
in a context free fashion.

When the named subexpressions are inlined, they work more like the way
pronouns in English work:

  When the (named subexpressions as they) are inlined, they work more
like the way pronouns in English work.

It's a matter of setting up a subexpression for a subsequent
backreference, rather than pulling it out into a truly independent
step.

> And it's a lot more natural and easy to reason about than letting b escape one step out to the conditional expression but not any farther. (Or to the rest of the complete containing expression? Or the statement? What does "x[(a.b as b)] = b" mean, for example? Or "x[(b if (a.b as b) else a.c) + (b if (d.b as b) else d.c)]"? Or "x[(b if (a.b as b) else a.c) + b]"?)

Exactly, that's the main problem with named subexpressions - if you
let them *always* leak, you get some very confusing consequences, and
if you *never* let them leak, than you don't address the if statement
and while loop use cases.

So to make them work as desired, you have to say they "sometimes"
leak, and then define what that means in a comprehensible way.

One possible way to do that would be to say that they *never* leak by
default (i.e. using a named subexpression always causes the expression
containing them to be executed in its own scope), and then introduce
some form of special casing into if statements and while loops to
implicitly extract named subexpressions.

> As a side note, the initial proposal here was to improve performance by not repeating the a.b lookup; I don't think adding an implicit comprehension-like function definition and call will be faster than a getattr except in very uncommon cases. However, I think there are reasonable cases where it's more about correctness than performance (e.g., the real expression you want to avoid evaluating twice is next(spam) or f.readline(), not a.b), so I'm not too concerned there. Also, I'm pretty sure a JIT could effectively inline a function definition plus call more easily than it could CSE an expression that's hard to prove is static.

Yes, I'm not particularly interested in speed here - I'm personally
interested in maintainability and expressiveness. (That's also why I
consider this a very low priority project for me personally, as it's
very, very hard to make a programming language easier to use by
*adding* concepts to it. You really want to be giving already emergent
patterns names and syntactic sugar, since you're then replacing
learning a pattern that someone would have eventually had to learn
anyway with learning the dedicated syntax).

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia


More information about the Python-ideas mailing list