# [Python-ideas] Statements vs Expressions... why?

Arnaud Delobelle arnodel at googlemail.com
Sun Sep 14 11:08:08 CEST 2008

```On 14 Sep 2008, at 09:44, Cliff Wells wrote:

> On Sun, 2008-09-14 at 01:36 -0700, Cliff Wells wrote:
>> On Sun, 2008-09-14 at 01:25 -0700, Cliff Wells wrote:
>>> On Sun, 2008-09-14 at 08:36 +0100, Arnaud Delobelle wrote:
>>>> On 14 Sep 2008, at 07:36, Cliff Wells wrote:
>>>>
>>>>> On Sun, 2008-09-14 at 07:23 +0100, Arnaud Delobelle wrote:
>>>>>> On 13 Sep 2008, at 23:17, Cliff Wells wrote:
>>>>>>
>>>>>>> On Sat, 2008-09-13 at 19:01 +0100, Arnaud Delobelle wrote:
>>>>>>>>
>>>>>>>> So what does:
>>>>>>>>
>>>>>>>> a = (if False: 1)
>>>>>>>>
>>>>>>>> evaluate to?
>>>>>>>
>>>>>>> That's a good question.  This is one of those areas where a
>>>>>>> definition
>>>>>>> would need to be created.  My inclination is to say None (much
>>>>>>> like a
>>>>>>> function with no return statement).
>>>>>>>
>>>>>>
>>>>>> Assuming the return value of "None", I go back to an example I
>>>>>> gave
>>>>>> earlier:
>>>>>>
>>>>>>   factors = for x in range(2, n):
>>>>>>       if n % x == 0:
>>>>>>           x
>>>>>>
>>>>>> This doesn't work as intended (filtering out the non-factors).
>>>>>> How
>>>>>> to
>>>>>> make it work?  The only way I can think of is to make (if 0: 1)
>>>>>> return
>>>>>> a special "non-value" which loops will then filter out.  But
>>>>>> then we
>>>>>> all know what happens to non-values.
>>>>>>
>>>>>> So how would you solve this problem?
>>>>>
>>>>> By writing it properly ;-)
>>>>>
>>>>> factors = for x in range ( 2, n ):
>>>>>   if n % x == 0:
>>>>>       yield x
>>>>>
>>>>> As I mentioned previously, in order to merge the concept of
>>>>> generator
>>>>> with a for-expression would require bringing in the yield keyword,
>>>>> just
>>>>> as it does now for generator functions.
>>>>>
>>>>> The example you gave would evaluate to None (or perhaps an empty
>>>>> list or
>>>>> generator - that's a detail that would take more consideration
>>>>> before
>>>>> defining it).
>>>>>
>>>>
>>>> OK, but this seems to me incompatible with current Python:
>>>>
>>>> def chain(I, J):
>>>>     for i in I: yield i
>>>>     for j in J: yield j
>>>>
>>>> Currently
>>>>
>>>>>>> '-'.join(chain('spam', 'eggs'))
>>>> 's-p-a-m-e-g-g-s'
>>>>
>>>> With your proposal, the first *expression* (for i in I: yield i)
>>>> will
>>>> evaluate to something like iter(I) and then be discarded.  Then the
>>>> second *expression* (for j in J: yield j) will evaluate to
>>>> something
>>>> like iter(J) which will be discarded.  So chain('spam', 'eggs')
>>>> will
>>>> return None.
>>>
>>> It seems you have me on this one.  There's clearly other ways to
>>> do the
>>> same thing, but since backwards-compatibility is a prerequisite I'll
>>> have to concede the point.
>>>
>>> A potential solution would to be to use a different keyword than
>>> "yield"
>>> to separate current syntax from my proposed syntax (that is,
>>> distinguish
>>> expression-scoped yield from function-scoped yield), and just side-
>>> step
>>> the issue, but that seems unappealing to me.
>>
>> Actually, a few more minutes of pondering got me a solution I don't
>> find
>> abhorrent.  Let yield mean what it currently does.  Instead, let
>> "continue" accept an optional parameter (like "return").  Then your
>> above example continues to work and my proposed change would look
>> like:
>>
>> for i in j:
>>    continue i
>>
>> I haven't considered this too deeply, but it is at least consistent
>> with
>> "return" syntax and doesn't add a new keyword.
>
> I'm probably replying way too fast (in fact, I know I am), but I have
> two thoughts on this:
>
> 1) it seems to alter the semantics of "continue" too much when
> considered against current syntax, but...
>
> 2) with the new syntax, it seems not too bad because
>
> j = range(3)
> for i in j: i  # evaluates to []
> for i in j: continue # evaluates to []
> for i in j: continue i # evaluates to [0,1,2]
>
> Overall I'm a bit torn on the idea.  Thoughts?
>

Let's not call it continue, but YIELD for now:

for i in J: YIELD i

Now this won't work for nested loops. E.g. in current python

def flatten(I):
for J in I:
for j in J:
yield j

>>> '-'.join(flatten(['spam', 'eggs']))
's-p-a-m-e-g-g-s'

Now say you want to write that inline with a for-expression:

'-'.join(
for J in I:
for j in J:
YIELD j
)

That won't work because the j's will be accumulated in the inner loop
and the outer loop won't accumulate anything, therefore returning an
empty iterable. The flatten() example above works because the the
scope of the yield statement is clearly defined by the enclosing def
statement.  To make it work, not only you need a special  yield
expression but you also need a special for expression:

'-'.join(
FOR J in I:
for j in J:
YIELD j
)

Here it is clear what happens: the YIELD accumulates values in the FOR
loop.  Not very coder friendly though :)

Now compare with the current syntax:

'-'.join(j for J in I for j in J)

> Regards,
> Cliff
>

--
Arnaud

```