Most active commenters
  • jcelerier(4)
  • jstimpfle(3)
  • bluecalm(3)
  • TooBrokeToBeg(3)
  • arkadiytehgraet(3)

←back to thread

2024 points randlet | 63 comments | | HN request time: 1.508s | source | bottom
1. js2 ◴[] No.17516019[source]
Background ("PEP 572 and decision-making in Python"):

https://lwn.net/Articles/757713/

replies(2): >>17516132 #>>17516693 #
2. jcelerier ◴[] No.17516132[source]
> The problem with the C-style of assignments is that it leads to this classic error: if(x = 0) {...}

yeah, if you code on 20 years old compilers with no warnings. GCC 4.1 warns about this (with -Wall) and Clang 3.4 warns about this too, without any warning flag.

replies(4): >>17516174 #>>17516220 #>>17517715 #>>17518178 #
3. Waterluvian ◴[] No.17516174[source]
I think that having strong opinions on how others should be developing software is how communities become toxic like this.

"Shooting yourself in the foot is your fault for not using a linter that detects accidental misuse of assignment!"

replies(3): >>17516217 #>>17516742 #>>17516875 #
4. jcelerier ◴[] No.17516217{3}[source]
> I think that having strong opinions on how others should be developing software is how communities become toxic like this.

Depends on what you think the answer to "is programming an art or a science" is. People who build bridges are absolutely subject to "strong opinions" on how to build bridges. I am of the opinion that shipping software to others when under a contract without using all the facilities available to prevent problems - linting, static type checking, etc - should be considered at best a breach of contract and ideally criminal negligence.

replies(4): >>17516414 #>>17516463 #>>17516818 #>>17520374 #
5. jstimpfle ◴[] No.17516220[source]
Not having a problem in the first place is preferable to fighting it with tools. == vs = is a classic mistake that I'm sure every C programmer has wasted some time on. (I'm just undecided if I prefer dealing with this very problem occasionally, or choosing either of the verbosity of := assignments or the non-orthogonality of = assignment statements plus := assignment expressions.)
replies(2): >>17516712 #>>17517362 #
6. Waterluvian ◴[] No.17516414{4}[source]
You're right. There are times where being right is absolutely imperative.

Maybe it's a know it when you see it kind of thing. When the debate isn't killing people, it's potentially inconvenient extra coding by a developer, and to appreciate the immense social cost those debates can have.

7. icebraining ◴[] No.17516463{4}[source]
Programming languages are not just used for shipping software to others when under a contract. Specially Python, which is often used as a teaching language.
8. bluecalm ◴[] No.17516693[source]
Well, when he is dealing with arguments as silly as "but we will confuse = with ==) it's not a surprise he is tired and had enough. I just wish he had pushed through C style assignment before retiring. New syntax is ugly and introduces a new operator which basically does the same thing. When I've seen this PEP for the first time I thought he lost his touch after so many fantastic design decisions throughout the years. Now I realize he just had enough.

Thanks Guido for fantastic language. I wouldn't find love for programming if it wasn't for you.

replies(3): >>17516852 #>>17517193 #>>17518184 #
9. bluecalm ◴[] No.17516712{3}[source]
Well, the best option would be to have := (or any other operator) as C style assignment in the first place. This is huge backward compatibility breakage though so the second best option is to use =.

You can require additional brackets around assignment if you use the returning value (or otherwise it's syntax error). They did that with the new one anyway.

replies(1): >>17517599 #
10. TooBrokeToBeg ◴[] No.17516742{3}[source]
I'm still puzzled to why would a compiler allow assignment in an explicit conditional (outside of loop syntax). It's like a baked-in blindspot that most people just want to ignore for some reason. Some languages actually guard against this well enough (eg Kotlin) and say "don't". Even with guards in place, it's not all that complicated to work around in the edge cases where you might want to do it.
replies(5): >>17517091 #>>17517115 #>>17519127 #>>17519910 #>>17519933 #
11. woah ◴[] No.17516818{4}[source]
Thinking that linting and static typing will stop all errors is foolish (although I prefer both, myself). Cargo culting "best practices" is often useless, and is sometimes used as a crutch by developers who are bad at the much more important and much less quantifiable things, like code readability and architectural simplicity.

Wanting your pet coding preferences to be enforced by criminal law is a sadomasochistic fantasy.

replies(2): >>17517481 #>>17520898 #
12. Tehnix ◴[] No.17516852[source]
> Well, when he is dealing with arguments as silly as "but we will confuse = with ==)...

You clearly never had exposure to a PHP codebase. If people can trip up, people will trip up.

13. Dayshine ◴[] No.17516875{3}[source]
Linter?

You mean compiler right? The compiler should be warning for things like this, not relying on 3rd party tools.

So, yes, if you're using a custom compiler, you kinda are to blame for shooting yourself in the foot. Just use the official binaries!

replies(2): >>17517020 #>>17518135 #
14. dvlsg ◴[] No.17517020{4}[source]
Depends on the language. Javascript, for example, would use a linter for this warning.
15. arkadiytehgraet ◴[] No.17517091{4}[source]
Funny you should mention Kotlin; while I like the language a lot, I believe this particular feature would be of immense help in the following scenario:

Imagine you have a sealed class Foo, with `class Bar(x: String) : Foo()` and `Baz() : Foo()`.

Now, imagine you have a method, returning an object of type Foo: `fun foo(): Foo`

And you want to pattern match on the result of this method:

  when(foo()) {
      is Bar -> ...
      is Baz -> ...
  }
Now, the problem is: how do you access String field x in the first branch? The only way to do it now is to extract the methos call into redundant local variable, and then pattern match it instead of `foo()` directly as I did above.

Now imagine Kotlin had that feature; then we could just do the following:

  when(foo = foo()) {
    is Bar -> foo.x
    is Baz -> ...
  }
replies(2): >>17517234 #>>17520246 #
16. kelnos ◴[] No.17517115{4}[source]
Because assignment is an expression. It has nothing specifically to do with the position being a conditional.

Conditionals need an expression, and assignment fits the bill, so it works.

replies(3): >>17518108 #>>17519195 #>>17520103 #
17. joshuamorton ◴[] No.17517193[source]
Guido (along with most core devs) are actively against c-style assignment. This pep was a compromise that Guido felt was good.

But c-style assignment in Python was never going to happen, not should it have.

18. bpicolo ◴[] No.17517234{5}[source]
One option is match statements. Great way to make this sort of inline assignment unnecessary
replies(1): >>17517267 #
19. arkadiytehgraet ◴[] No.17517267{6}[source]
Could you please elaborate more on what you mean by match statements? Is it an already existing feature of Kotlin?
replies(1): >>17517291 #
20. bpicolo ◴[] No.17517291{7}[source]
Ah, I guess `when` is literally Kotlin's equivalent of match.

What I'm thinking of here is Rust's match statements, which do give you the ability to make use of those intermediary values by making use of Rust's enum type.

https://doc.rust-lang.org/book/second-edition/ch06-02-match....

replies(1): >>17517337 #
21. arkadiytehgraet ◴[] No.17517337{8}[source]
I see; I agree that proper pattern matching would indeed solve that as well, as e.g. Scala does.
22. sbjs ◴[] No.17517362{3}[source]
A programming language and the tools you use with it are tightly integrated: you're never using a language, you're always using a language via a tool. And if all C compilers you might use have warnings enabled for this buggy expression, then there's no problem.
replies(3): >>17519011 #>>17519479 #>>17519488 #
23. jcelerier ◴[] No.17517481{5}[source]
> Thinking that linting and static typing will stop all errors is foolish (although I prefer both, myself).

Thinking that putting a seatbelt will prevent death in a crash is foolish, and yet they are still mandatory

replies(1): >>17518410 #
24. thomasahle ◴[] No.17517599{4}[source]
> You can require additional brackets around assignment if you use the returning value (or otherwise it's syntax error). They did that with the new one anyway.

They only did it if the := is at the root level. The following is completely legal:

    if match := re.search(pat, text):
        `print("Found:", match.group(0))
or

    [y := f(x), y**2, y**3]
replies(1): >>17518152 #
25. iainmerrick ◴[] No.17517715[source]
What do you do if you really do intend the assignment?

(Assume for the sake of argument it’s some more reasonable example like "if (x = re_match(foo, bar)) {...}")

replies(3): >>17517838 #>>17517895 #>>17518252 #
26. 613style ◴[] No.17517838{3}[source]
You wrap it in parens to avoid the warning: if ((x = 7)) { ... }
27. Tyr42 ◴[] No.17517895{3}[source]
You do "if (((x = re_match(foo, bar))) {...}".

At least the systems I've seen need a extra (()) around = to disable the warnings and notify the reader that you're not just comparing for equality.

28. KSteffensen ◴[] No.17518108{5}[source]
Why is assignment an expression and not a statement? Why should an assignment evaluate to a value?
29. KSteffensen ◴[] No.17518135{4}[source]
It's not that easy for the compiler to provide these warnings when the language is interpreted rather than compiled
replies(1): >>17519210 #
30. bluecalm ◴[] No.17518152{5}[source]
Great, I haven't used := thing yet. I think it's more elegant like that.
31. raverbashing ◴[] No.17518178[source]
Warnings in Python are very rare, and only for good reasons

Throwing warnings for "common code" would be uncharacteristic.

32. sevensor ◴[] No.17518184[source]
My reaction was the opposite -- I didn't know about PEP572, read it immediately after this retirement announcement. My response was, "where has this feature been all these years?" I'm delighted by the addition -- it removes a lot of "loop and a half" ugliness without adding do loops, it simplifies list comprehensions, it lets you remove opportunities to make mistakes, and it doesn't let you put a single-equals assignment inside a conditional as a typo.
replies(1): >>17519038 #
33. thestoicattack ◴[] No.17518252{3}[source]
C++17 has if-statement initializers, so you can do

    if (auto x = re_match(foo, bar); x.has_value()) {
(or x.ok() or whatever the name is of the method that tells you the validity of x)
34. mkesper ◴[] No.17518410{6}[source]
Here you are very wrong. Look at https://www.cdc.gov/motorvehiclesafety/seatbelts/facts.html#... or https://en.wikipedia.org/wiki/Seat_belt
replies(1): >>17518926 #
35. jcelerier ◴[] No.17518926{7}[source]
that's exactly what I mean. Seatbelts won't save you every time but they save you enough times that they are mandatory.

> In another study that examined injuries presenting to the ER pre- and post-seat belt law introduction, it was found that 40% more escaped injury and 35% more escaped mild and moderate injuries.[83]

I don't know how many bugs can use of all possible explicit typing and compiler warnings avert but I'd wager that it would be at least as high a percentage.

replies(1): >>17519733 #
36. orf ◴[] No.17519011{4}[source]
No, your using a language, and if you know the language enough you begin using a tool
37. sneakermoney ◴[] No.17519038{3}[source]
Same here, I often find myself writing a loop-and-a-half and will on occasion repeat expressions to get around extra lines (if I cared about performance, I wouldn't be writing it in Python).

I feel like a lot of the resistance is from people who think this is somehow bad style, because it's associated with a source of bugs in other languages. The same kind of people will argue endlessly against having 'goto' in a language, even when it can clearly make for cleaner code (eat it, Dijkstra!) in some cases.

replies(2): >>17520460 #>>17522652 #
38. shakna ◴[] No.17519127{4}[source]
It's handy in C for guarding parts of the language, as you often have to check return values anyway.

    if(malloc(10000 * sizeof foo) == NULL) {
      // Error processing
    } // No need for an else branch here.
replies(1): >>17519309 #
39. TooBrokeToBeg ◴[] No.17519195{5}[source]
> Because assignment is an expression. It has nothing specifically to do with the position being a conditional.

No, but it has a lot to do with the way humans write code. This is the source of the bug, not the logic. eg Why bother having whitespace that the compiler can't use (typically)? Human readability.

The concession to not take into account human failing, is pathological.

40. TooBrokeToBeg ◴[] No.17519210{5}[source]
The compiler can do anything a linter can do. Multi-pass multithreaded compilers are not unheard of. eg While compiling to temporary storage, I do a linting in another thread. At the end, check for successes and move the artifacts into the output directory.
replies(1): >>17519893 #
41. the-dude ◴[] No.17519309{5}[source]
Missing assingment.
replies(1): >>17519481 #
42. ◴[] No.17519479{4}[source]
43. shakna ◴[] No.17519481{6}[source]

    int success = NULL;

    if(success = malloc(...)) {
    }
    if(success = malloc(...)) {
    }
    if(success = malloc(...)) {
    }
replies(1): >>17519737 #
44. jstimpfle ◴[] No.17519488{4}[source]
The most important "program" that reads your source code is probably in your brain. You probably have some "tooling" in their that is on the watch for "if (x = y)" and other patterns, but I'm sure you'd prefer to not have to run it.

As another counterexample let me give you "language tooling". For example, write a tool that generates a FFI from $LANGUAGE to C. Is it really so unimportant that $LANGUAGE is clean and simple and easy to parse?

replies(3): >>17520132 #>>17520708 #>>17521430 #
45. wrmsr ◴[] No.17519733{8}[source]
And as a former motorcyclist the idea of putting a seatbelt on a bike is terrifying, but under the umbrella of 'mandatory' I'd have to risk getting cut in half in a fender bender 'because that's how it's done'. If you blindly follow guidelines without understanding why they were put in place and when they should be circumvented you do more harm than good. In this context specifically, a python thread, I've not even found formatters that 'just work' in python to the degree that they do in java - I get and support significant whitespace but that combined with the crazy calling convention frequently lead me to just have to suffix spans of lines with noqa because I'm communicating to the reader something that a dumb bot doesn't understand. And that's not even getting into the heavier stuff, I type annotate everything but mypy is beyond useless for my relevant codebases - it no longer just outputs walls of useless noise it straight up crashes, from the most minimal use of even descriptors much less metaclasses. Tools and policies are essential but at the end of the day humans and situations are still currently authoritative.
46. slavik81 ◴[] No.17519737{7}[source]
That will result in undefined behaviour if the value returned by malloc is greater than INT_MAX. If you really need an integer, use intptr_t. But, generally it makes more sense to just use a pointer.

    char* p = NULL;
    if (p = malloc(...)) {
      ...
    }
47. TwelveNights ◴[] No.17519893{6}[source]
What the poster means is that a compiler may not be as applicable to interpreted languages.
48. wool_gather ◴[] No.17519910{4}[source]
Well, if you have the concept of Maybe/Optional, it's quite handy to have a conditional binding as control flow (as in Swift):

    guard let value = myOptional else {
        // Handle absence
    }
    // Use value
Or

    if let value = myOptional {
        // Use value
    }
49. emmelaich ◴[] No.17519933{4}[source]
Read Tim Peter's essay on the end of the PEP 572.

It's very good.

50. hinkley ◴[] No.17520103{5}[source]
The other partial fix is to stop allowing truthy conditionals. Only allow Boolean values and the only time you it wrong is when you’re assigning to booleans.
51. sbjs ◴[] No.17520132{5}[source]
> "The most important "program" that reads your source code is probably in your brain."

Not if you have a good Integrated Development Environment (or IDE for short)! That's an even better "program" than your brain, because it shows syntax errors and other warnings right next to your code. It will literally put little red squiggly lines right under `if (x = 1)` and show the file as red in your file explorer side-bar, and when you hover over this line it will give you a tool tip saying "this assigns a new value, it's probably not what you meant, I can either turn it into x == 1 or ((x = 1)), which one do you want?" That is the power of IDEs and they are amazing. We should consider them an integral part of writing code and not fight them or be afraid of them!

replies(1): >>17520454 #
52. bjz_ ◴[] No.17520246{5}[source]
In Rust and Haskell you can do:

    match foo() {
        Bar @ foo => foo.x,
        Baz => ...,
    }
53. jodrellblank ◴[] No.17520374{4}[source]
I feel like you could have written this https://jjj.blog/2016/08/todays-software-is-terrible/
54. b0rsuk ◴[] No.17520454{6}[source]
You can't assume that every single person will have a good IDE. And you will be reading and maintaining code written by people who don't. In that case I prefer to deal with a language which guarantees no trivial errors.
replies(1): >>17520713 #
55. bogomipz ◴[] No.17520460{4}[source]
Can you explain what a "loop-and-a-half" is in Python?
replies(1): >>17520537 #
56. blake_himself ◴[] No.17520537{5}[source]

    x = stuff
    while x:
        x = stuff
replies(1): >>17521139 #
57. skywhopper ◴[] No.17520708{5}[source]
I disagree... I don’t really have this problem though. = and == are pretty visually distinct to me. As much as > and < or & and @ (or == and :=).

But the best solution is probably to just drop the = as an operator altogether.

58. skywhopper ◴[] No.17520713{7}[source]
I agree with your points about IDEs but alas no such language exists to keep you from simple errors.
59. vehementi ◴[] No.17520898{5}[source]
> Thinking that linting and static typing will stop all errors is foolish

Did you reply to the wrong post?

60. Delgan ◴[] No.17521139{6}[source]
On the contrary, loop-and-a-half is intended to avoid repeating stuff outside and inside of the loop body.

    while true:
        x = stuff
        if not x:
            break
replies(1): >>17533866 #
61. jstimpfle ◴[] No.17521430{5}[source]
> that $LANGUAGE is clean

s/\$LANGUAGE/C/

62. sevensor ◴[] No.17522652{4}[source]
I think EWD was basically in the right to push hard for structured programming, but I also like Knuth's nuanced take:

https://pic.plover.com/knuth-GOTO.pdf

63. chucksmash ◴[] No.17533866{7}[source]
GPs example is perfectly valid as well - this is just a restatement of the same logic in a way that keeps the logic contained within the loop at the cost of using a conditional plus a break inside of an unconditional loop. See [0]:

  Another motivating code pattern is the "loop and a half".
 
  It was once common for processing a file by line, but that 
  has been solved by making file objects iterable; however 
  other non-iterable interfaces still suffer from patterns 
  like:
  
  line = f.readline()
  while line:
      ...  # process line
      line = f.readline()
  
  or like this:
  
  while True:
      line = f.readline()
      if not line:
          break
      ... # process line

  Either of those could be replaced with a much more clear
  and concise version using an assignment expression:

  while line := f.readline():
      ... # process line
[0]: https://lwn.net/Articles/757713/