[Python-ideas] Language proposal: variable assignment in functional context

Brice PARENT contact at brice.xyz
Wed Jun 21 04:31:05 EDT 2017


I might not have understood it completely, but I think the use cases 
would probably better be splitted into two categories, each with a 
simple solution (simple in usage at least):

*When we just want a tiny scope for a variable:*

Syntax:

with [assignment]:
     # use of the variable

# assigned variable is now out of scope

Examples:

with b = a + 1:
     y = b + 2

# we can use y here, but not b

or

with delta = lambda a, b, c: b**2 - 4 * a * c:
     x1 = (- b - math.sqrt(delta(a, b, c))) / (2 * a)
     x2 = (- b + math.sqrt(delta(a, b, c))) / (2 * a)

# delta func is now out of scope and has been destroyed

We don't keep unnecessarily some variables, as well as we don't risk any 
collision with outer scopes (and we preserve readability by not 
declaring a function for that).

It would probably mean the assignment operator should behave differently 
than it does now which could have unexpected (to me) implications. It 
would have to support both __enter__ and __exit__ methods, but I don't 
know if this makes any sense. I don't know if with a + 1 as b: would 
make a better sense or be a side-effect or special cases hell.

*When we want to simplify a comprehension:*

(although it would probably help in many other situations)

Syntax:

prepare_iterable(sequence, *functions)

which creates a new iterable containing tuples like (element, 
return_of_function_1, return_of_function_2, ...)

Examples:

[m(spam)[eggs] for _, m inprepare_iterable(sequence, lambda obj: obj[0].field.method) if m]

or, outside of a comprehension:

sequence = [0, 1, 5]
prepare_iterable(sequence, lambda o: o * 3, lambda o: o + 1)
# -> [(0, 0, 1), (1, 3, 2), (5, 15, 6)]

The "prepare_iterable" method name might or might not be the right word 
to use. But English not being my mother language, I'm not the right 
person to discuss this...
It would be a function instead of a method shared by all iterables to be 
able to yield the elements instead of processing the hole set of data 
right from the start.
This function should probably belong to the standard library but 
probably not in the general namespace.

-- Brice

Le 17/06/17 à 12:27, Steven D'Aprano a écrit :
> On Sat, Jun 17, 2017 at 09:03:54AM +0200, Sven R. Kunze wrote:
>> On 17.06.2017 02:27, Steven D'Aprano wrote:
>>> I think this is somewhat similar to a suggestion of Nick Coghlan's. One
>>> possible syntax as a statement might be:
>>>
>>> y = b + 2 given:
>>>      b = a + 1
>> Just to get this right:this proposal is about reversing the order of
>> chaining expressions?
> Partly. Did you read the PEP?
>
> https://www.python.org/dev/peps/pep-3150/
>
> I quote:
>
>      The primary motivation is to enable a more declarative style of
>      programming, where the operation to be performed is presented to the
>      reader first, and the details of the necessary subcalculations are
>      presented in the following indented suite.
>      [...]
>      A secondary motivation is to simplify interim calculations in module
>      and class level code without polluting the resulting namespaces.
>
> It is not *just* about reversing the order, it is also about avoiding
> polluting the current namespace (global, or class) with unnecessary
> temporary variables. This puts the emphasis on the important part of the
> expression, not the temporary/implementation variables:
>
>      page = header + body + footer where:
>          header = ...
>          body = ...
>          footer = ...
>
> There is prior art: the "where" and "let" clauses in Haskell, as well as
> mathematics, where it is very common to defer the definition of
> temporary variables until after they are used.
>
>
>> Instead of:
>>
>> b = a + 1
>> c = b + 2
>>
>> we could write it in reverse order:
>>
>> c = b + 2 given/for:
>>      b = a + 1
>
> Right. But of course such a trivial example doesn't demonstrate any
> benefit. This might be a better example.
>
> Imagine you have this code, where the regular expression and the custom
> sort function are used in one place only. Because they're only used
> *once*, we don't really need them to be top-level global names, but
> currently we have little choice.
>
> regex = re.compile(r'.*?(\d*).*')
>
> def custom_sort(string):
>      mo = regex.match(string)
>      ... some implementation
>      return key
>
> # Later
> results = sorted(some_strings, key=custom_sort)
>
> # Optional
> del custom_sort, regex
>
>
> Here we get the order of definitions backwards: the thing we actually
> care about, results = sorted(...), is defined last, and mere
> implementation details are given priority as top-level names that
> either hang around forever, or need to be explicitly deleted.
>
> Some sort of "where" clause could allow:
>
> results = sorted(some_strings, key=custom_sort) where:
>      regex = re.compile(r'.*?(\d*).*')
>
>      def custom_sort(string):
>          mo = regex.match(string)
>          ... some implementation
>          return key
>
>
> If this syntax was introduced, editors would soon allow you to fold the
> "where" block and hide it. The custom_sort and regex names would be
> local to the where block and the results = ... line.
>
> Another important use-case is comprehensions, where we often have to
> repeat ourselves:
>
> [obj[0].field.method(spam)[eggs] for obj in sequence if obj[0].field.method]
>
> One work around:
>
> [m(spam)[eggs] for m in [obj[0].field.method for obj in sequence] if m]
>
> But perhaps we could do something like:
>
> [m(spam)[eggs] for obj in sequence where m = obj[0].field.method if m]
>
> or something similar.
>
>
>
>> If so, I don't know if it just complicates the language with a feature
>> which does not save writing nor reading
> It helps to save reading, by pushing less-important implementation
> details of an expression into an inner block where it is easy to ignore
> them. Even if you don't have an editor which does code folding, it is
> easy to skip over an indented block and just read the header line,
> ignoring the implementation. We already do this with classes, functions,
> even loops:
>
>      class K:
>          ... implementation of K
>
>      def func(arg):
>          ... implementation of func
>
>      for x in seq:
>          ... implementation of loop body
>
>      page = header + body + footer where:
>          ... implementation of page
>
>
> As a general rule, any two lines at the same level of indentation are
> read as being of equal importance. When we care about the implementation
> details, we "drop down" into the lower indentation block. But when
> skimming the code for a high-level overview, we skip the details of
> indented blocks and focus only on the current level:
>
>      class K:
>      def func(arg):
>      for x in seq:
>      page = header + body + footer where:
>
>
> (That's why editors often provide code folding, to hide the details of
> an indented block. But even without that feature, we can do it in our
> own head, although not as effectively.)
>
>
>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20170621/ff4ec2b5/attachment-0001.html>


More information about the Python-ideas mailing list