<div dir="auto"><div><div class="gmail_quote"><div dir="ltr">On Wed, Jul 4, 2018, 09:09 Steven D'Aprano <<a href="mailto:steve@pearwood.info" target="_blank" rel="noreferrer">steve@pearwood.info</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">On Wed, Jul 04, 2018 at 12:10:11AM -0700, Nathaniel Smith wrote:<br>
<br>
> Right, Python has a *very strong* convention that each line should<br>
> have at most one side-effect, <br>
<br>
import math, fractions, decimal<br>
<br>
(PEP 8 be damned, sometimes, especially in the REPL, this is much <br>
better than three separate imports.)<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">Sure, the nice thing about "very strong convention" versus "strict rule" is that there's a bit of lee-way at the edges. I'm pretty sure PEP 572 would not be accepted if it's proponents were expecting it to be forbidden by PEP 8 though.</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
values = [mapping.pop(key) for key in (1, 3, 6, 15)]<br>
<br>
Admittedly, that's an awfully specific case. But would you really <br>
re-write that as:<br>
<br>
values = [mapping.pop(1)]<br>
values.append(mapping.pop(3)<br>
values.append(mapping.pop(6)<br>
values.append(mapping.pop(15)<br>
<br>
just to keep the "one side-effect per line" rule? I wouldn't.<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">I don't think I've ever seen something like this in real life, but sure, I'd say it's pushing the edge of good taste, but maybe just squeaking under the line.</div><div dir="auto"><br></div><div dir="auto">A more common edge case for my rule would be:</div><div dir="auto"><br></div><div dir="auto">x = d.pop()</div><div dir="auto"><br></div><div dir="auto">where one could argue that this is doing two things: (1) removing an item from a container, and (2) storing it in a variable. But that's ok for me argument, because my argument is about how people represent the code in their mind so they can reason about it, and "take a value from *here* and put it *there*" is still simple enough to feel like a single operation, and there's no possibility of confusion about what order things happen in. "Import these three modules" is similar.</div><div dir="auto"><br></div><div dir="auto">This is also why I'm ok with</div><div dir="auto"><br></div><div dir="auto">if x := fn():</div><div dir="auto"><br></div><div dir="auto">But my mind stack-overflows when trying to read</div><div dir="auto"><br></div><div dir="auto">if (x := fn()) is None:</div><div dir="auto"><br></div><div dir="auto">The first is a complex operation, that both triggers a jump and mutates a variable, which is two side-effects. But the two side-effects are closely linked and cannot affect each other, so I can think of it as one "save and jump" operation. In the second case, there's clearly 3 separate phases that I have to simulate in my head: first the mutation of 'x', then switching back to expression evaluation to do the comparison with None, and then the 'if'.</div><div dir="auto"><br></div><div dir="auto"><span style="font-family:sans-serif">And of course it gets much worse in the PEPs motivating examples, where a variable is created and used within the same expression.</span><br></div><div dir="auto"><br></div><div dir="auto">This is a whole mode of reasoning about code that python has never required before (in practice), and even if you think := is fabulous you should still at least be able to acknowledge that this is a huge shift.</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
Anyway, since "one side-effect per line" is just a convention, there's <br>
absolutely no reason why it cannot remain a convention. Don't do this:<br>
<br>
values = (x := expensive_func(1, 2))+(y := expensive_func(3, 4)) + x*y<br>
<br>
unless you really have to. It's just common sense.<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">I think this really underestimates the importance of this kind of convention in how people learn and use the language, and how the language and it's conventions influence each other.</div><div dir="auto"><br></div><div dir="auto">If list.sort returned self, then people would absolutely write things like</div><div dir="auto"><br></div><div dir="auto">mylist.sort().reverse()</div><div dir="auto"><br></div><div dir="auto">And then we'd have lint rules and arguments about it and we'd have to waste precious brain power deciphering this in other people's code and arguing about it in code reviews. By not returning 'self', the language clearly takes a position that you shouldn't do this, and cuts off all that discussion.</div><div dir="auto"><br></div><div dir="auto">The := operator is exactly the opposite: it's *only purpose* is to break this convention, which is a pretty strong statement that now we are supposed to embed side effects inside expressions, and be prepared to read code that does this.</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
Conventions are good. If they'e enough to stop people writing:<br>
<br>
mylist = mylist.sort() or mylist.reverse() or mylist<br>
<br>
they'll be good enough to stop people stuffing every line full of <br>
assignment expressions.<br>
<br>
<br>
<br>
> and that if it does have a side-effect<br>
> it should be at the outermost level.<br>
<br>
I don't understand what that means. Do you mean the global scope?<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">I mean something like "the top node of the statement's AST".</div><div dir="auto"><br></div><div dir="auto">A normal statement: print([a])</div><div dir="auto"><br></div><div dir="auto">Technically only one side-effect in this line, but it's in a weird place: [print(a)]</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><br>
> I think the most striking evidence for this is that during the<br>
> discussion of PEP 572 we discovered that literally none of us –<br>
> including Guido – even *know* what the order-of-evaluation is inside<br>
> expressions.<br>
<br>
I'm not sure that "literally none of us" is quite true, after all the <br>
code is deterministic and well-defined and surely whoever maintains it <br>
probably understands it, but even if it were true, I don't see the <br>
connection between "we don't know the order of operations" and "Python <br>
has a rule no more than one-side effect per line". Seems a pretty <br>
tenuous ccomclusion to draw.<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">Side-effects and sequencing are intimately linked. When you have side-effects, you have to think about their order; contrariwise, in pure code, order of evaluation is a meaningless concept. Therefore, Python programmers have to know the order of evaluation inside expressions exactly to the extent that they embed side-effecting operations inside of expressions.</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
<br>
> In fact PEP 572 now has a whole section talking about the<br>
> oddities that have turned up here so far, and how to fix them. Which<br>
> just goes to show that even its proponents don't actually think that<br>
> anyone uses side-effects inside expressions, because if they did, then<br>
> they'd consider these changes to be compatibility-breaking changes.<br>
<br>
If you're talking about fixing the scoping issues inside classes, that's <br>
no longer part of the PEP (although the current version hasn't been <br>
updated to reflect that). The weirdness of class scopes already exist. <br>
This might, at worst, make it more visible, but it isn't going to make <br>
the problem worse.<br>
<br>
If you're talking about something else, I can't think of what it might <br>
be. Can you explain?<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">I'm talking about things like the PEP's discussion of what order keys and values should be evaluated in dict displays. Previously, no one knew or cared about this order (outside from, yes, a tiny handful of people maintaining this bit of the compiler). Now that we're adding a language construct whose whole purpose is to embed side-effects inside expressions, this suddenly becomes something we all have to actually think about.</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
<br>
> Some people make fun of Python's expression/statement dichotomy,<br>
> because hey don't you know that everything can be an expression,<br>
> functional languages are awesome hurhur, <br>
<br>
Oh, you mean functional languages like Rust, Javascript, and Ruby?<br>
<br>
(Or at least, *almost* everything is an expression.)<br>
<br>
The functional programming paradigm is a lot more than just "everything <br>
is an expression", and very much *non*-functional languages like <br>
Javascript can be designed to treat everything as an expression.<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">Sure, obviously languages can be designed that way. And there's a tradition which argues that all languages *should* be designed that way, which I'm hand-wavily attributing to "functional folks" – it goes back to Lisp, ultimately, and in functional languages having a distinction between statements and expressions *is* weird and artificial. JS, Ruby, and Rust aren't functional languages, but in this respect they were very much influenced by this functional tradition.</div><div dir="auto"><br></div><div dir="auto">Python quite intentionally diverges. Why? Why bother distinguishing statements and expressions at all? Python doesn't normally introduce concepts like this for no reason. Is JS just a more elegant and thoughtfully designed language than Python? Had Guido just never heard of Lisp? Obviously not.</div><div dir="auto"><br></div><div dir="auto">Python has a statement/expression dichotomy exactly because Python is imperative, and the basic program structure is "do this, and then do this, and then do this", where each "do this" is one side-effecting operation. In this context it's super helpful to have a really clear, privileged syntactic encoding of the things you "do" (statements) and their relative ordering (suites).</div><div dir="auto"><br></div><div dir="auto">-n</div></div>