[Python-ideas] Alternative to PEP 532: delayed evaluation of expressions

Nick Coghlan ncoghlan at gmail.com
Mon Nov 7 03:23:31 EST 2016


On 6 November 2016 at 23:06, Eric V. Smith <eric at trueblade.com> wrote:
> Creating a new thread, instead of hijacking the PEP 532 discussion.
>
> From PEP 532:
>
>> Abstract
>> ========
>>
>> Inspired by PEP 335, PEP 505, PEP 531, and the related discussions, this
>> PEP
>> proposes the addition of a new protocol-driven circuit breaking operator
>> to
>> Python that allows the left operand to decide whether or not the
>> expression
>> should short circuit and return a result immediately, or else continue
>> on with evaluation of the right operand::
>>
>>     exists(foo) else bar
>>     missing(foo) else foo.bar()
>
> Instead of new syntax that only works in this one specific case, I'd prefer
> a more general solution. I accept being "more general" probably seals the
> deal in killing any proposal!

Being more general isn't the goal here: just as with decorators,
context managers, and coroutines, the goal is pattern extraction based
on idioms that people *already use* (both in Python and in other
languages) to make them shorter (and hence easier to recognise), as
well as to give them names (and hence make them easier to learn, teach
and find on the internet).

Consider the conditional expression usage examples in the PEP:

    value1 = expr1.field.of.interest if expr1 is not None else None
    value2 = expr2["field"]["of"]["interest"] if expr2 is not None else None
    value3 = expr3 if expr3 is not None else expr4 if expr4 is not
None else expr5

Which would become the following in the basic form of the PEP:

    value1 = missing(expr1) else expr1.field.of.interest
    value2 = missing(expr2) else expr2.["field"]["of"]["interest"]
    value3 = exists(expr3) else exists(expr4) else expr5

or else the following in the more comprehensive variant that also
allows short-circuiting "if" expressions (and would hence mainly
include "missing()" as the answer to "What does 'not exists()'
return?"):

    value1 = expr1.field.of.interest if exists(expr1)
    value2 = expr2.["field"]["of"]["interest"] if exists(expr2)
    value3 = exists(expr3) else exists(expr4) else expr5

Now, how would those look with a lambda based solution instead?

    value1 = eval_if_exists(expr, lambda: expr1.field.of.interest)
    value2 = eval_if_exists(expr, lambda: expr2["field"]["of"]["interest"])
    value3 = first_existing(expr3, lambda: expr4, lambda: expr5)

This is not an idiom people typically use, and that's not solely due
to them finding the "lambda:" keyword unattractive or function calls
being much slower than inline expressions. We can swap in a Java
inspired "()->" operator to help illustrate that (where "()" indicates
an empty parameter list, and the "->" indicates the LHS is a parameter
list and the RHS is a lambda expression):

    value1 = eval_if_exists(expr, ()->expr1.field.of.interest)
    value2 = eval_if_exists(expr, ()->expr2["field"]["of"]["interest"])
    value3 = first_existing(expr3, ()->expr4, ()->expr5)

Without even getting into the subtle differences between inline
evaluation and nested scopes in Python, the fundamental challenge with
this approach is that it takes something that was previously about
imperative conditional control flow and turns it into a use case for
callback based programming, which requires an understanding of custom
first class functions at the point of *use*, which is conceptually a
much more difficult concept than conditional evaluation.

By contrast, while as with any other protocol you would need to
understand object-oriented programming in order to define your own
circuit breakers, *using* circuit breakers would only require
knowledge of imperative condition control flow, as well as how the
specific circuit breaker being used affects the "if" and "else"
branches.

Regards,
Nick.

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


More information about the Python-ideas mailing list