Is PEP 572 really the most effective way to solve the problems it's targeting?
<opinion> I have to say I'm not overly thrilled with PEP 572...it's almost odd, because if you asked me back when I first joined this list when I was 13, I would've no doubt said *YES*. But, since then, I've gone across many projects and languages, and fundamentally *I have never felt hurt by the lack of assignment in an expression*, and I always regretted every time I tried it in C or Crystal. I understand this experience is pretty insignificant in comparison to many of the wizards here, but I thought I'd still share it as an opener for what I'm about to say. </opinion> With this being said, I'd encourage everyone to take a bit of a step back: what exactly are people looking for in PEP 572? I see two main goals: - Assignment in a conditional structure. - Assignment in a list comprehension. Most other use cases would significantly hurt readability and seem pretty rare. Now let's break down the top one: - Assignment in an if condition. - Assignment in a while condition. So there are roughly three main goals here overall. Now, are there better ways to solve these? (FWIW C solved the while condition one with the C-style for loop, but I'm pretty sure few people here would really go for that.) C++ has recently solved the if condition by allowing declarations inside the conditions: if (auto a = 123; a != 456) { Many languages have a 'let' expression (using Felix as my example): if let a = 1, b = 2 in a == b then Swift has taken a bit of a hybrid between the above two: if let a = 1, b = 2, a == b { Now, what's the common theme here? **Declarations should be separate from expressions.** We've got languages that range from baggage-filled to functional to a bit of all of the above, and none of them have added assignment *inside* an expression. The argument is roughly the same across all boards: you're putting major but easy-to-miss side effects in the midst of expressions that *seem* pure. All this is to say: I'd really encourage everyone here to think a bit more about *why* exactly you want this feature, and then think if there's really no better way. Any solution that separates declarations would be far more readable, (arguably) more Pythonic, and play more nicely with the new-ish typing features to boot I understand reluctance to add a syntax exception like this, but I really feel it'd be worth it. As a side note, I was a strong supporter of comprehension generalizations, f-strings, *and* dataclasses. However, this proposal seems a bit ugly and excessive for what it's trying to accomplish. P.S. Yes, the unmatched curly braces were intentional to drive you crazy for a few hours. I blame Randall Monroe. You're welcome. -- Ryan (ライアン) Yoko Shimomura, ryo (supercell/EGOIST), Hiroyuki Sawano >> everyone else https://refi64.com/
On Wed, Apr 25, 2018 at 09:36:31PM -0500, Ryan Gonzalez wrote:
<opinion>
I have to say I'm not overly thrilled with PEP 572...it's almost odd, because if you asked me back when I first joined this list when I was 13, I would've no doubt said *YES*.
I have the opposite experience: I've warmed to it the more I have read it. Before Chris had even written this PEP, there was a Python-Ideas thread asking for dedicated syntax in comprehensions alone that would have been equivalent to a binding-expression. I was rather negative about the whole thing (but no where near as negative as I've been in the past when this has come up before), until Chris pointed out that such binding-expressions have value outside of comprehensions. [...]
Now, what's the common theme here? **Declarations should be separate from expressions.**
Declarations and assignments are not the same thing.
We've got languages that range from baggage-filled to functional to a bit of all of the above, and none of them have added assignment *inside* an expression.
And your claim about assignment inside expressions is only true if you ignore the languages where assignment is an expression with a return value. You know, strange and exotic languages with hardly any users or influence, like C, C++, C#, Java, Javascript, Ruby, Perl, PHP, Lisp ... -- Steve
On April 25, 2018 11:05:04 PM Steven D'Aprano
Steven D'Aprano writes:
On Wed, Apr 25, 2018 at 09:36:31PM -0500, Ryan Gonzalez wrote:
Now, what's the common theme here? **Declarations should be separate from expressions.**
Declarations and assignments are not the same thing.
Ryan mostly meant "initialization" rather than "declaration", I suspect. In C's for() statement, the first clause in parentheses is initialization, the third is assignment. FWIW, I had been thinking the same thing (that what is really wanted is initialization clauses in loop statements) but had no concrete suggestions. Now I too have warmed to the binding expression approach (partly because Guido has, so I'm preparing for the inevitable :^), mostly because of Tim's "humorous observation" about use in printf debugging. The fact that there is this use case independent of block variable initialization (ie, in a loop or if statement) is quite attractive to me. It's true, as I think Antoine pointed out, that it's easy enough to define a wrapper function that prints a value and returns it. But that's not as flexible, and if you have more than one variable to "watch", you either need an argument to the wrapper to provide the variable's name, or different functions for different variables.
On Thu, 26 Apr 2018 20:35:20 +0900
"Stephen J. Turnbull"
Steven D'Aprano writes:
On Wed, Apr 25, 2018 at 09:36:31PM -0500, Ryan Gonzalez wrote:
Now, what's the common theme here? **Declarations should be separate from expressions.**
Declarations and assignments are not the same thing.
Ryan mostly meant "initialization" rather than "declaration", I suspect. In C's for() statement, the first clause in parentheses is initialization, the third is assignment.
FWIW, I had been thinking the same thing (that what is really wanted is initialization clauses in loop statements) but had no concrete suggestions. Now I too have warmed to the binding expression approach (partly because Guido has, so I'm preparing for the inevitable :^), mostly because of Tim's "humorous observation" about use in printf debugging. The fact that there is this use case independent of block variable initialization (ie, in a loop or if statement) is quite attractive to me.
It's true, as I think Antoine pointed out, that it's easy enough to define a wrapper function that prints a value and returns it.
That wasn't me, but I agree with the idea anyway :-) Regards Antoine.
[Ryan Gonzalez
I have to say I'm not overly thrilled with PEP 572...it's almost odd, because if you asked me back when I first joined this list when I was 13, I would've no doubt said *YES*. But, since then, I've gone across many projects and languages, and fundamentally *I have never felt hurt by the lack of assignment in an expression*, and I always regretted every time I tried it in C or Crystal. I understand this experience is pretty insignificant in comparison to many of the wizards here, but I thought I'd still share it as an opener for what I'm about to say.
The older you get, the more you'll regret not still being 13 ;-)
With this being said, I'd encourage everyone to take a bit of a step back: what exactly are people looking for in PEP 572?
I see two main goals:
- Assignment in a conditional structure. - Assignment in a list comprehension.
Most other use cases would significantly hurt readability and seem pretty rare.
I haven't been much impressed by suggested uses outside conditional contexts either.
Now let's break down the top one:
- Assignment in an if condition. - Assignment in a while condition.
So there are roughly three main goals here overall. Now, are there better ways to solve these? ... C++ has recently solved the if condition by allowing declarations inside the conditions:
But C++ has always had assignment expressions. This:
if (auto a = 123; a != 456) {
is solving a different (albeit related) problem: that C/C++ require declaring variables before use. Python doesn't. They could have done the same via, .e.g,, { auto a = 123; if (a != 456) { ... } } and still have had the scope of `a` limited to one block. auto-initializers in conditionals just gave a bit of syntactic sugar for what was already easily (although with more typing) done.
Many languages have a 'let' expression (using Felix as my example):
if let a = 1, b = 2 in a == b then
I don't read Felix, but I assume the _scope_ of `a` & `b` there ends immediately before the "then". If the names can't be used in the _body_ of a Python `if` (or `while`) block, it's essentially useless to allow binding names for use solely in the conditional test. So it would help if you picked "real Python examples" from the many other earlier messages in these threads. Python expressions can't span Python statement boundaries - only Python blocks can do that. A form of `let` that _would_ work would be block-structured: let m = regexp.match(pattern. line) in: if m: print(m.group(0)) That solves "a scope problem" the current version of the PEP gave up on, but in all other respects seems a step back from the current: m = regexp.match(pattern, line) if m: print(m.group(0))
Swift has taken a bit of a hybrid between the above two:
if let a = 1, b = 2, a == b {
That seems plain incoherent ;-)
Now, what's the common theme here? **Declarations should be separate from expressions.** We've got languages that range from baggage-filled to functional to a bit of all of the above, and none of them have added assignment *inside* an expression.
C++ and C have always had assignment expressions . Ditto Java, Javascript, Perl, Icon, ... (many, many others). I don't see a good reason to grant that Felix and Swift are necessarily improvements over the former (with the exception of Icon, which I'm merely fond of) very widely used languages.
The argument is roughly the same across all boards: you're putting major but easy-to-miss side effects in the midst of expressions that *seem* pure.
All this is to say: I'd really encourage everyone here to think a bit more about *why* exactly you want this feature, and then think if there's really no better way. Any solution that separates declarations would be far more readable, (arguably) more Pythonic, and play more nicely with the new-ish typing features to boot
People have been trying for years. If you come up with a realistic (for Python) idea, that's great - share it! But it's probably better suited to python-ideas than python-dev.
...
On 2018-04-25 19:36, Ryan Gonzalez wrote: By now we've searched over the current proposal in excruciating detail. However, there were two good questions in this message which I haven't seen addressed yet: - How are other modern languages solving this issue? - How does this new construct intersect with typing functionality? -Mike
Mike Miller wrote:
- How are other modern languages solving this issue?
In all the languages I can think of that allow assignments in expressions, there is only one assignment operator -- a stand alone assignment is just a bare assignment expression. But those languages were all designed that way from the start. I'm not aware of any that began by forbidding assignment in expressions and then added it later. -- Greg
[Mike Miller]
- How are other modern languages solving this issue?
[Greg Ewing
In all the languages I can think of that allow assignments in expressions, there is only one assignment operator -- a stand alone assignment is just a bare assignment expression.
Pretty much so, but I don't know what "modern" means to Mike. The R language may set a record for, umm, innovation here: """ There are three different assignment operators: two of them have leftwards and rightwards forms.[1] """ So there are 5 assignment operator spellings in R: = <- -> <<- ->> Note that the link doesn't tell the whole story either; e.g., they don't all have the same precedence level. And, in addition to the 5 infix spellings shown above, there are also prefix (looks like a 2-argument function call) spellings. Back on Earth ;-) , I think it's worth it to point out that only languages (with assignment expressions) aping C use "=" for assignment and "==" for equality. That was a Really Bad Idea that all other (not aping C) languages I know of avoided. But I'm not sure any of this is relevant to what Mike meant by "this issue".
But those languages were all designed that way from the start. I'm not aware of any that began by forbidding assignment in expressions and then added it later.
Me neither. It's certainly the case that Guido would not have designed a language that aped C's poor decision here. At its very start, Python used "=" for both assignment and equality testing (and == was a syntax error). So I think it's evident that, at the time, he didn't envision ever adding assignment expressions. [1] https://www.rdocumentation.org/packages/base/versions/3.5.0/topics/assignOps
On Thu, Apr 26, 2018 at 09:36:48AM -0700, Mike Miller wrote:
However, there were two good questions in this message which I haven't seen addressed yet:
- How are other modern languages solving this issue? - How does this new construct intersect with typing functionality?
What counts as a modern language? Less than five years old? Less than fifty years old? Are Javascript, Ruby and R modern? They all support assignment as expressions. I think Koitlin, Rust and Go prohibit assignment as expressions. Swift assignment evaluates as Void (equivalent to None in Python, I guess), so you can use assignment in an expression but it returns nothing and only operates by side-effect. As far as type hints go, I think that if you need explicit type hints in the middle of an expression, it's a bad idea and you ought to pull it out as a separate statement. That applies regardless of whether that expression involves binding or not. -- Steve
Rust has a few syntactic ways to accomplish the same thing, though. I think match expressions are used for the equivalent of conditionals that carry the condition value into the body of the expression, and all blocks return the result of the last statement, so you can do things like:
let mut x;
while { x = foo(); x } {
bar(x);
}
I don't know if that's idiomatic Rust (and I wrote it on a phone and didn't check to be sure it compiles), but it does more or less solve the problem of assignment in a control flow condition.
On April 27, 2018 12:58:16 AM UTC, Steven D'Aprano
On Thu, Apr 26, 2018 at 09:36:48AM -0700, Mike Miller wrote:
However, there were two good questions in this message which I haven't seen addressed yet:
- How are other modern languages solving this issue? - How does this new construct intersect with typing functionality?
What counts as a modern language? Less than five years old? Less than fifty years old? Are Javascript, Ruby and R modern? They all support assignment as expressions.
I think Koitlin, Rust and Go prohibit assignment as expressions.
Swift assignment evaluates as Void (equivalent to None in Python, I guess), so you can use assignment in an expression but it returns nothing and only operates by side-effect.
As far as type hints go, I think that if you need explicit type hints in the middle of an expression, it's a bad idea and you ought to pull it out as a separate statement. That applies regardless of whether that expression involves binding or not.
-- Steve _______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/paul%40ganssle.io
Rust has a few syntactic ways to accomplish the same thing, though. I
On Fri, Apr 27, 2018 at 10:52 AM Paul G
let mut x; while { x = foo(); x } { bar(x); }
Go is similar to Python; it's doesn't allow assignment in expression. And Go has similar syntax like above; for x := foo(); x { bar(x) } if err := baz(); err != nil { return err } I like Go and I think this syntax can be ported to Python. But it help only if/while statements. It doesn't help list comprehension. And Go doesn't have list comprehension.
Sorry all, wasn't specific enough. By "modern" I mean the last decade perhaps. New languages that have had a chance to look at the older generations and choose their best ideas, while leaving behind the rest. Personally I thought of Swift (Ryan mentioned), Kotlin, Rust, and perhaps Go, though Go wasn't focused on breaking new ground outside of ease of concurrency. I don't know R or Felix at all, but sound interesting. Nim is another I'm vaguely aware of. They surely have given some thought to the issue. One thing that jumped out at me is that most replies here jumped to the question of whether they supported assignment-expressions, but that is only one potential solution. To be more clear, I wondered how did they solve "the problem itself?" Was their solution different? Ryan somewhat alluded to that, but I'd like to dig in a bit on that part. In contrast, in many of the other threads I heard, "C, C++, C#, Java, etc do assignment-expressions, they're useful and not so hard to learn." Ok that's reasonable, but where is the industry headed? Python deferred long enough that we don't necessarily have to choose a classic solution. So, it sounds like many of the new generation of languages are not embracing these expressions everywhere but rather letting folks do an assignment right in the statement where their use case applies, if, while, maybe comprehensions. Is that accurate? Looks like I've got some homework to do, haha. -Mike On 2018-04-26 17:58, Steven D'Aprano wrote:
What counts as a modern language? Less than five years old? Less than fifty years old? Are Javascript, Ruby and R modern? They all support assignment as expressions.
I think Koitlin, Rust and Go prohibit assignment as expressions.
Swift assignment evaluates as Void (equivalent to None in Python, I guess), so you can use assignment in an expression but it returns nothing and only operates by side-effect.
As far as type hints go, I think that if you need explicit type hints in the middle of an expression, it's a bad idea and you ought to pull it out as a separate statement. That applies regardless of whether that expression involves binding or not.
On April 27, 2018 12:16:09 AM Mike Miller
On 04/27/2018 05:35 AM, Ryan Gonzalez wrote: [snip] Ryan, the quoted text in your emails is not being marked as such, meaning that the entire email appears to be from you. This makes it really difficult to pick out your responses. I would really appreciate it of you could figure out why that is happening and resolve it. (I have no clue so can't offer any ideas.) Thanks. -- ~Ethan~
participants (10)
-
Antoine Pitrou
-
Ethan Furman
-
Greg Ewing
-
INADA Naoki
-
Mike Miller
-
Paul G
-
Ryan Gonzalez
-
Stephen J. Turnbull
-
Steven D'Aprano
-
Tim Peters