Discussion:
Special keyword denoting an infinite loop
Thomas Allen
2014-06-28 08:04:24 UTC
Permalink
Rust language defines a special way to make an infinite loop (
http://doc.rust-lang.org/tutorial.html#loops).

I propose adding the same keyword to Python. It will be very useful for
WSGI servers and will suit as a more convenient replacement for recursion
(hence Python doesn't do TRE). I personally find it much prettier than *while
True* or *while 1*. It won't cause any problems with existing programs,
because *loop* is very rarely used as a variable name.

For instance
do_something()
do_something_else()
would turn to
do_something()
do_something_else()
Steven D'Aprano
2014-06-28 09:11:12 UTC
Permalink
Post by Thomas Allen
Rust language defines a special way to make an infinite loop (
http://doc.rust-lang.org/tutorial.html#loops).
Do they give an explanation for why they use a keyword for such a
redundant purpose?
Post by Thomas Allen
I propose adding the same keyword to Python. It will be very useful for
WSGI servers and will suit as a more convenient replacement for recursion
(hence Python doesn't do TRE).
I understand that *infinite loops* themselves are useful, and that
recursion can be replaced by iteration, but how does the "loop" keyword
solve these issues better than "while True"?
Post by Thomas Allen
I personally find it much prettier than *while
True* or *while 1*.
If the only advantage of this is that you personally find it prettier,
then I'm a strong -1 on this suggestion.

* I personally find it less elegant than "while True". "while True"
tells you explicity what it does: it's a while loop, and it operates
while True is true (i.e. forever). "loop" looks like an incomplete
line: what sort of loop, while, repeat or for? Loop for how long? It's
all implicit.

* It's yet another special keyword to memorise. It doesn't eliminate the
need to know "while", or to know "True", and it gives you no extra
benefit. It's just completely redundant.


Suppose "loop" becomes a keyword in Python 3.5. That means that every
existing Python program that uses "loop" as a function or variable
cannot work in Python 3.5. It also means that any Python 3.5 code that
uses the "loop" keyword

Adding new keywords is only done for the most critical reasons, or when
there is no other good alternative, not just on a whim. There is already
a perfectly good way to write infinite loops, adding the "loop" keyword
doesn't add anything to the language, it just breaks working code for
the sake of a minor, cosmetic change.
Post by Thomas Allen
It won't cause any problems with existing programs,
because *loop* is very rarely used as a variable name.
How do you know it is rare? I've written code where loop is a name:

def main():
setup()
loop()
--
Steven
Stefan Behnel
2014-06-28 10:05:43 UTC
Permalink
Post by Steven D'Aprano
Post by Thomas Allen
It won't cause any problems with existing programs,
because *loop* is very rarely used as a variable name.
setup()
loop()
Also, this example only uses English names. There is no reason to assume
that programmers with other native languages that (also) use ASCII letters
or transliterations would not happen to have a word spelled "loop" in their
language that they may commonly use in their programs. Or that programmers
with a lower level of proficiency in the English language would also not
consider "loop" a good name for a variable or function. Or that there is no
technical/business/science/social/you-name-it terminology whatsoever that
makes "loop" appear as the most obvious choice for a name in a program of
that domain. "Obvious Reasoning" easily fails when it comes to
understanding naming decisions, especially across cultural boundaries.

Adding a new keyword needs very serious reasoning, and that's a good thing.

Stefan
r***@public.gmane.org
2014-06-30 17:24:37 UTC
Permalink
Post by Stefan Behnel
Adding a new keyword needs very serious reasoning, and that's a good thing.
For pedantry's sake, I will note that "NAME ':'" is not a valid sequence
to start a statement with today. That is, however, probably _not_ a road
anyone wants to go down if there is any other option. It's almost enough
to make one wish that Python had defined an expansive set of reserved
words as Javascript does - a set which might not contain "loop" but
would probably contain "do".

What about _just_ "while:" or "for:"?
Steven D'Aprano
2014-06-30 18:20:30 UTC
Permalink
Post by r***@public.gmane.org
Post by Stefan Behnel
Adding a new keyword needs very serious reasoning, and that's a good thing.
[...]
Post by r***@public.gmane.org
What about _just_ "while:" or "for:"?
Why bother? Is there anything you can do with a bare "while:" that you
can't do with "while True:"? If not, what's the point?
--
Steven
Ron Adam
2014-07-01 00:16:38 UTC
Permalink
Post by Steven D'Aprano
Post by r***@public.gmane.org
Post by Stefan Behnel
Adding a new keyword needs very serious reasoning, and that's a good thing.
[...]
Post by r***@public.gmane.org
What about _just_ "while:" or "for:"?
Why bother? Is there anything you can do with a bare "while:" that you
can't do with "while True:"? If not, what's the point?
It looks like (in python3) "while 1:", "while True:", and while with a
string, generates the same byte code. Just a bare SETUP_LOOP. Which would
be the exact same as "while:" would. So no, it wouldn't make a bit of
difference other than saving a few key strokes in the source code.
... while True:
... break
...
Post by Steven D'Aprano
Post by r***@public.gmane.org
Post by Stefan Behnel
L()
dis(L)
2 0 SETUP_LOOP 4 (to 7)

3 >> 3 BREAK_LOOP
4 JUMP_ABSOLUTE 3
Post by Steven D'Aprano
Post by r***@public.gmane.org
7 LOAD_CONST 0 (None)
10 RETURN_VALUE
... while 1:
... break
...
Post by Steven D'Aprano
Post by r***@public.gmane.org
Post by Stefan Behnel
dis(LL)
2 0 SETUP_LOOP 4 (to 7)

3 >> 3 BREAK_LOOP
4 JUMP_ABSOLUTE 3
Post by Steven D'Aprano
Post by r***@public.gmane.org
7 LOAD_CONST 0 (None)
10 RETURN_VALUE
... while "looping":
... break
...
Post by Steven D'Aprano
Post by r***@public.gmane.org
Post by Stefan Behnel
dis(LLL)
2 0 SETUP_LOOP 4 (to 7)

3 >> 3 BREAK_LOOP
4 JUMP_ABSOLUTE 3
Post by Steven D'Aprano
Post by r***@public.gmane.org
7 LOAD_CONST 0 (None)
10 RETURN_VALUE


Cheers, Ron

Devin Jeanpierre
2014-06-28 12:53:08 UTC
Permalink
Post by Steven D'Aprano
Post by Thomas Allen
Rust language defines a special way to make an infinite loop (
http://doc.rust-lang.org/tutorial.html#loops).
Do they give an explanation for why they use a keyword for such a
redundant purpose?
Sure. "while true {...}" would require magic by the compiler to make
it optimized and to make things like the following pass compile-time
checks: "let a; while true { a = 1; break;}; return a". With a while
loop, Rust can't really know that the loop executes even once without
special-casing the argument, so it emits a compile-time error because
the variable a might be uninitialized. If Rust magically knew about
while true, then it becomes confusing if replacing "true" with
something the compiler doesn't directly understand causes the compiler
to get confused.

Special cases aren't special enough to break the rules, so Rust
decides that the special case here deserves its own keyword.

In Python, there is no special case at all, so there is no extra
keyword. As it should be.

Rust has some ideas Python could borrow, but this ain't one of them. -1.

-- Devin
Nick Coghlan
2014-06-28 15:16:20 UTC
Permalink
This post might be inappropriate. Click to display it.
Chris Angelico
2014-06-28 10:07:17 UTC
Permalink
I personally find it much prettier than while True or while 1.
One common technique I've seen is the self-documenting infinite loop:

while "more work to be done":
get_work()
do_work()

If you're worried about the prettiness of "while True", this might
help. Since any non-empty string counts as true, this can add a bit
more information without disrupting the loop itself.

ChrisA
Nick Coghlan
2014-06-28 10:24:40 UTC
Permalink
Post by Thomas Allen
Rust language defines a special way to make an infinite loop
(http://doc.rust-lang.org/tutorial.html#loops).
I propose adding the same keyword to Python. It will be very useful for WSGI
servers and will suit as a more convenient replacement for recursion (hence
Python doesn't do TRE). I personally find it much prettier than while True
or while 1. It won't cause any problems with existing programs, because loop
is very rarely used as a variable name.
"won't cause any problems" does not mesh with an assertion of "very
rarely used" on two counts:

- "very rarely" means it *will* cause problems for at least some programs
- the "very rarely used" assertion isn't backed by any analysis

However, it's a useful example for illustrating some good questions to
ask about any proposals to change the language:

1. What else will have to change as a consequence?
2. Who will be hurt by this change, and how much will they be hurt?
3. Who will gain from this change, and how much will they gain?

I'm going to work through and answer all of these for this proposal -
this isn't to pick on you, it's to show the kind of thinking that may
lie behind a terse "No" or "That's a terrible idea" when a dev is
pressed for time and isn't able to write out their full rationale for
disliking a suggestion :)

Starting from the top:

1. What else will have to change as a consequence?

In this case, a quick search over CPython itself for "loop" variables finds:

- the "asyncore.loop" public API
- parameters named "loop" in the asyncio public API

Really, we can stop there - a new keyword that conflicts with public
APIs in the standard library just won't happen without an
extraordinarily compelling reason, and it's unlikely such a reason is
going to be suddenly discovered for a language that has already been
around for more than 20 years. However, I'll continue on to illustrate
how even a quick check like running "pss --python loop" from a CPython
checkout (which is all I did to come up with these examples) can
recalibrate our intuitions about variable names and the impact of
introducing new keywords.

Additional uses of "loop" as a name in CPython:

- many internal variables named "loop" in asyncio and its test suite
- a call to asyncore.loop in the smtpd standard library module
- a "loop" counter in the hashlib standard library module
- calls to asyncore.loop in the test suite for the asyncore standard
library module
- a call to asyncore.loop in the test suite for the asynchat standard
library module
- a call to asyncore.loop in the test suite for the poplib standard
library module
- a call to asyncore.loop in the test suite for the ftplib standard
library module
- a call to asyncore.loop in the test suite for the logging standard
library module
- a call to asyncore.loop in the test suite for the ssl standard library module
- a call to asyncore.loop in the test suite for the os standard library module
- a "loop" attribute in the test suite for the cyclic garbage collector
- a "loop" variable in the test suite for the faulthandler module
- a "loop" variable in the test suite for the signal module
- a "loop" variable in the ccbench tool (used to check GIL tuning parameters)

In addition to the above cases that actually *do* use "loop", there
are plenty of other cases called things like "_loop", "mainloop" or
"cmdloop", that could easily have been called just "loop" instead. The
"very rarely used" claim doesn't hold up, even just looking at the
standard library. It's a relatively *domain specific* variable name,
but that's not the same as being rare - in the applicable domain, it
gets used a *lot*.

The search shows that "loop" is also used in many comments as a
generic term, and I know from personal experience that is often used
as an umbrella term where saying "loop statement" encompasses both for
loops and while loops.

2. Who will be hurt by this change, and how much will they be hurt?

- anyone affected by the backwards compatibility break for asyncore and asyncio
- anyone with an existing variable called "loop" (which includes the
core dev team)
- anyone used to using "loop statement" as an umbrella term (which includes me)
- anyone tasked with explaining why there's a dedicated alternative
spelling for "while True:" and "while 1:" in a way that students can
grasp easily (the compiler can already detect and optimise them with
their existing spelling, so the keyword isn't needed for that. Even
static analysis tools can pick up the explicit infinite loops pretty
easily. That only leaves the readability argument, which has a certain
amount of merit as described below)

3. Who will gain from this change, and how much will they gain?

- future learners of Python may more easily grasp that "while
True:"/"while 1:" infinite loops tend to serve a fundamentally
different purpose than normal while loops. Unfortunately, Rust chooses
to allow both the "infinite loop" and the normal "while loop" to be
used to implement loop-and-a-half semantics, so it doesn't actually
make that distinction - "loop" is literally just an alternative
spelling of "while true", that provides no additional hints as to
whether or not "break" might be present in the loop body.

For Python, the backwards compatibility issues make the idea of "loop"
as a new keyword a clear loss, and there's insufficient gain in the
idea in general to be worth pursuing it further.

I don't think the referenced feature actually makes much sense as part
of Rust either, but starting afresh means it is at least harmless,
albeit a little redundant. If it disallowed "break", you'd at least
have a clear indicator that "this is the last statement in this
execution unit - the only way out now is to return to our caller".

Regards,
Nick.
--
Nick Coghlan | ncoghlan-***@public.gmane.org | Brisbane, Australia
Nick Coghlan
2014-06-28 15:14:53 UTC
Permalink
rather than a special keyword in Python, how about having Python to support
the concept of passing block (a group of statements) as argument? I thought
that can be quite elegant solution. So a loop statement can be interpreted
simply as a function that accept a block e.g. loop [block]?
Supporting block has a lot of practical applications. I remember seeing some
special purpose flow control functions as early as Tcl. We also see it in
Ruby and the more recently the new Swift language.
This is a well worn path, and it's difficult to retrofit to an
existing language. Ruby, at least, relies heavily on a convention of
taking blocks as the last argument to a function to make things work,
which is a poor fit to Python's keyword arguments and far more varied
positional signatures for higher order functions.

PEP 403 and PEP 3150 are a couple of different explorations of the
idea a more block-like feature.
http://python-notes.curiousefficiency.org/en/latest/pep_ideas/suite_expr.html
is one that goes even further to consider a delineated subsyntax for
Python that would allow entire suites as expressions.

However, the stumbling block all these proposals tend to hit is that
proponents really, really, struggle to come up with compelling use
cases where "just define a named function" isn't a clearer and easier
to understand answer.

Cheers,
Nick.
--
Nick Coghlan | ncoghlan-***@public.gmane.org | Brisbane, Australia
Andrew Barnert
2014-06-28 21:33:58 UTC
Permalink
Post by Nick Coghlan
rather than a special keyword in Python, how about having Python to support
the concept of passing block (a group of statements) as argument? I thought
that can be quite elegant solution. So a loop statement can be interpreted
simply as a function that accept a block e.g. loop [block]?
Supporting block has a lot of practical applications. I remember seeing some
special purpose flow control functions as early as Tcl. We also see it in
Ruby and the more recently the new Swift language.
This is a well worn path, and it's difficult to retrofit to an
existing language. Ruby, at least, relies heavily on a convention of
taking blocks as the last argument to a function to make things work,
which is a poor fit to Python's keyword arguments and far more varied
positional signatures for higher order functions.
Since Benny mentioned Swift, it's probably worth following up on that. Swift doesn't actually have blocks* (somewhat surprising, since Apple previously added blocks to ObjC and even C); it has a clever way of getting all the benefits of blocks without the downsides. Could there be something for Python there?

In Ruby (and ObjC) blocks and functions are different types of things. They're defined, called, and passed differently; they have different scope semantics (functions can't capture local variables, blocks can); they can't even easily be converted to each other.

Like Python, Swift functions are closures.

Swift functions can be defined in two ways, but either way, they're the same kind of function. Just like Python's def and lambda. Their func statement is almost exactly like our def statement except with braces. Their inline closure expression is similar to our lambda expression, but with some major differences (most of which have actually been proposed for Python): no keyword to introduce the expression, params go inside the braces, params can be anonymous (so { $1 + $2 } is a complete definition, equivalent to lambda _1, _2: _1 + _2), and of course they can be multiline and contain statements. (And like ours, return isn't necessary.)

Then Swift added one tiny pieces of syntactic sugar: if an anonymous function definition is the last argument in a function call, it can go outside the parens. So, you can write this:

reduce(myArray, 0) { $1 + $2 }
myArray.filter { $1 >= 0 }

That looks just like Ruby blocks, but it's still just functions. So if you already have a function defined out of line (or a bound method, or a function you received from elsewhere and stored in a variable, or whatever), you don't need to wrap it in a block, you just pass it:

reduce(myArray, "", smartConcat)
myArray.filter(myPredicate)

And that looks just like Python or Lisp.

And if you want to write a function that takes two functions, with an optional keyword argument after them, it can still take them both inline, quite readable. Ruby users claim they don't miss this ability, but anyone who uses promises in JS (or anything at all in Haskell) can think of dozens of times they passed non-final function arguments today, and wouldn't be happy with an API that made that impossible.

So, if we adopted Nick's not-really-serious proposals for omitting lambda before the colon when it's not syntactically ambiguous and allowing anonymous _1-style params, and added the syntactic sugar to allow lambdas as final arguments to come outside the parens, would that make Python better?

reduce(my_list, 0, lambda x, y: x + y)
reduce(my_list, 0) :_1 + _2

I think the general agreement on the first two changes was that, while they can be nice in a few cases, they can also be very ugly--and, more importantly, simple cases are already good enough today (see below), while more complex cases can't be done without multiline lambdas so there's no help. And I don't think the last bit of sugar sways things.

If we had a way to do multiline (statement-having) lambdas, that might be another story. But after years of trying, nobody's come up with a good solution, so you can't just assume that's solvable. Solve that first, and then we should definitely look at how the Swift stuff could be added on top of that.

Meanwhile, I think Nick's PEP 403, or something like it, is both more general and more Pythonic. Instead of trying to find a better way to embed function definitions into expressions, find a way to lift any subexpression out of an expression and define it (normally) after the current statement, and you solve the current problem for free, and a bunch of other problems too. (Although, unfortunately, none that really seem to demand solutions in practical code.)

---

* I lied a little at the top. Because Swift compiles into code that interacts with the ObjC runtime and Apple's blocks-filled C frameworks, of course it has to deal with ObjC blocks, in both directions. But it does this the same way it deals with C functions--by transparently bridging them to plain-old Swift functions. Unless you want to dig into the bridging using as-yet-undocumented stdlib functions, you never see blocks anywhere.
Nick Coghlan
2014-06-29 05:19:07 UTC
Permalink
Post by Andrew Barnert
Meanwhile, I think Nick's PEP 403, or something like it, is both more general and more Pythonic. Instead of trying to find a better way to embed function definitions into expressions, find a way to lift any subexpression out of an expression and define it (normally) after the current statement, and you solve the current problem for free, and a bunch of other problems too. (Although, unfortunately, none that really seem to demand solutions in practical code.)
On that last point, one of my goals at SciPy next month will be to
encourage folks in the scientific community that are keen to see
something resembling block support in Python to go hunting for
compelling *use cases*. The fatal barrier to proposals like PEP 403
and 3150 has long been that there are other options already available,
so the substantial additional complexity they introduce isn't
adequately justified. The two main stumbling blocks:

- generators-as-coroutines already offer a way of suspending execution
of a sequential operation, as embodied in asyncio.coroutine and
contexlib.contextmanager
- nested definitions of named functions are usually a readable
alternative in the cases lambdas can't handle

The reason I occasionally spend time on PEPs 403 and 3150 is because I
think we're missing a case where "one shot" functions could be handled
more gracefully - situations where we're defining a function solely
because we want to pass it to other code as an object at runtime, not
because we need to reference it at multiple places in the *source*
code. That's a pretty narrow niche, though - if you *do* need to
invoke the same code in multiple places, than a named function is
always going to be better, even if dedicated one-shot function support
is available.

Regards,
Nick.
--
Nick Coghlan | ncoghlan-***@public.gmane.org | Brisbane, Australia
Hernan Grecco
2014-06-29 12:53:31 UTC
Permalink
Hi
Post by Nick Coghlan
On that last point, one of my goals at SciPy next month will be to
encourage folks in the scientific community that are keen to see
something resembling block support in Python to go hunting for
compelling *use cases*. The fatal barrier to proposals like PEP 403
and 3150 has long been that there are other options already available,
so the substantial additional complexity they introduce isn't
What is the status of PEP 3150? I remember reading that you were
withdrawing 3150 in favor of 403 but this is not reflected in
http://legacy.python.org/dev/peps/pep-3150/.

cheers,

Hernán
Nick Coghlan
2014-06-29 13:37:43 UTC
Permalink
Post by Hernan Grecco
Hi
Post by Nick Coghlan
On that last point, one of my goals at SciPy next month will be to
encourage folks in the scientific community that are keen to see
something resembling block support in Python to go hunting for
compelling *use cases*. The fatal barrier to proposals like PEP 403
and 3150 has long been that there are other options already available,
so the substantial additional complexity they introduce isn't
What is the status of PEP 3150? I remember reading that you were
withdrawing 3150 in favor of 403 but this is not reflected in
http://legacy.python.org/dev/peps/pep-3150/.
I see merit in both alternatives, so I still update both of them
occasionally. I did withdraw 3150 at one point, but I later figured out a
possible solution to the previously fatal flaw in its namespace handling
semantics and moved it back to Deferred.

I tend not to announce any updates to either of them, since they'll remain
pure speculation in the absence of clear use cases where they would provide
a compelling readability benefit over the status quo.

Cheers,
Nick.
Post by Hernan Grecco
cheers,
Hernán
_______________________________________________
Python-ideas mailing list
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
Jonas Wielicki
2014-06-29 13:52:38 UTC
Permalink
Post by Nick Coghlan
Post by Hernan Grecco
Hi
Post by Nick Coghlan
On that last point, one of my goals at SciPy next month will be to
encourage folks in the scientific community that are keen to see
something resembling block support in Python to go hunting for
compelling *use cases*. The fatal barrier to proposals like PEP 403
and 3150 has long been that there are other options already available,
so the substantial additional complexity they introduce isn't
What is the status of PEP 3150? I remember reading that you were
withdrawing 3150 in favor of 403 but this is not reflected in
http://legacy.python.org/dev/peps/pep-3150/.
I see merit in both alternatives, so I still update both of them
occasionally. I did withdraw 3150 at one point, but I later figured out a
possible solution to the previously fatal flaw in its namespace handling
semantics and moved it back to Deferred.
It is still written in the abstract of 403 that 3150 was withdrawn.

regards,
jwi

p.s.: while I’m at it, in the “Explaining Decorator Clause Evaluation
and Application”, 3150 is missing a ?, I think, and in the “Out of Order
Execution” section there seems to be a markup issue after the second
code block
Post by Nick Coghlan
I tend not to announce any updates to either of them, since they'll remain
pure speculation in the absence of clear use cases where they would provide
a compelling readability benefit over the status quo.
Cheers,
Nick.
Post by Hernan Grecco
cheers,
Hernán
_______________________________________________
Python-ideas mailing list
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________
Python-ideas mailing list
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
Andrew Barnert
2014-06-29 07:22:02 UTC
Permalink
Sorry, just realized I left out the example I meant to give. I'll insert it below:

On Saturday, June 28, 2014 2:37 PM, Andrew Barnert <abarnert-/E1597aS9LQrN/***@public.gmane.orgc.invalid> wrote:

[snip]
Post by Andrew Barnert
And if you want to write a function that takes two functions, with an optional keyword argument after them, it can still take them both inline, quite readable. Ruby users claim they don't miss this ability, but anyone who uses promises in JS (or anything at all in Haskell) can think of dozens of times they passed non-final function arguments today, and wouldn't be happy with an API that made that impossible.
Here's some slightly simplified real-life JS code using Promises:

    db.select_one(sql, thingid)
    .then(function(rowset) { return rowset[0]['foo']; }, log_error);

Here's what the same code looks like with a Ruby port of Promises:

    db.select_one(sql, thingid)
    .then {|rowset| rowset[0]['foo'}
    .then(nil, proc {|err| log_error(err)})

I think this shows why blocks are a second-rate substitute for first-class, closure-capturing, inline-definable functions (which Ruby and ObjC don't have, but JS and Swift do). The only reason anyone should want blocks in Python is if they're convinced that it's impossible to come up with a clean syntax for multiline lambdas, but it's easy to come up with one for multiline blocks.
Mark Lawrence
2014-06-28 12:34:22 UTC
Permalink
Post by Thomas Allen
Rust language defines a special way to make an infinite loop
(http://doc.rust-lang.org/tutorial.html#loops).
I propose adding the same keyword to Python. It will be very useful for
WSGI servers and will suit as a more convenient replacement for
recursion (hence Python doesn't do TRE). I personally find it much
prettier than /while True/ or /while 1/. It won't cause any problems
with existing programs, because /loop/ is very rarely used as a variable
name.
For instance
do_something()
do_something_else()
would turn to
do_something()
do_something_else()
No thank you, my standard answer applies. I prefer Python in a Nutshell
to fit in my pocket, not the back of a 40 ton articulated lorry.
--
My fellow Pythonistas, ask not what our language can do for you, ask
what you can do for our language.

Mark Lawrence

---
This email is free from viruses and malware because avast! Antivirus protection is active.
http://www.avast.com
Loading...