If you know nothing about Python or coding, and you see a=b=c, you'd think that's true when all three a,b,c are the same. Python does that beautifully here, and that's the intent. It's not Python that's confusing, it's your previous experience with other languages that's confusing you.
I have some experience in computer language design. The issue here is that `a==b==c` expression is magical - that is it does not follow from extending comparison binary operator. Specifically, `==` is a binary operator that that compares an expression before and after and returns a boolean. In this case, A==B==C is a trinary comparison operator. This is normally ok, except it's rare and the symbol it is using is overloaded with binary comparison operator, so the people will be confused.
Once you know how it works and are used to it, I think it makes the code easier to parse... but there are heavy downsides. For example, it's not clear how short circuiting works. I've used python a bunch and logically I expect ```side_effect3()``` to not be evaluated if results of 1 and 2 are not equal: ```side_effect1() == side_effect2() == side_effect3()```. However, I do not know that for sure, while in other languages I would be able to reason about this from basic principles.
> the symbol it is using is overloaded with binary comparison operator, so the people will be confused
I think most people would expect expressions like `5 < x < 10` to work as they do in math, without necessarily thinking about it in terms of being an overloaded binary operator. The result in other languages that `5 < 12 < 10` = `true < 10` = `true` is more surprising, just that we've (perhaps by being bitten by it early on) gotten used to it.
Agree, also by reasoning from basic principles adding extra brackets ( ) should not change the output result - at least this is what our brain is programmed to believe, but in the case of chained operations it will.
Most programming languages ought not to be optimized for a non-programmer to read, but rather for someone who writes code to read.
There's a lot of options for a language to deal with a statement like this. It could be a syntax error, a compile or lint warning, it could work by doing order of operations and comparing the boolean from one compare with the third element, or it could work in the way you described.
I'd prefer languages that I work with to chose earlier options from that list. In most languages this sort of statement is usually a bug, and deserves explicit parenthesis to clarify the order of operations. I really don't want to have to pull out an operator precedence chart in order to read your code, much less have to look up some esoterica about this specific language.
I'd argue that many programming languages are not optimized for "people who read code" but they are optimized for programs who read code (compilers, interpreters) and programmers can stockholm-syndrome themselves into believing that it is targeted at them.
"someone who writes code" is very vague. For someone who writes code primarily in Python this behavior is less surprising than the rest that you described.
Most programming languages ought not to be optimized for a non-programmer to read
Python 'won' mostly because non-programmers could look at it, more or less understand what was going on, and feel that this was something they probably could learn.
What does A == B == C even mean? I mean I know what mathematically A=B=C means. That A, B and C are all equivalent.
But then is the mathematical '=' really a binary operator that maps two numerical inputs to a boolean output? It feels then as an abuse of notation if (A=B)=C doesn't allow A=B to change type?
Because I don't really have much use for a symbol that is some times doing a binary mapping from numbers to booleans and some times becomes some sort of ternary operator which maps 3 inputs to one boolean.
Maybe consider chained comparison operators early textual replacement along the lines of the C preprocessor, although the exact textual replacement would involve temporary variables to avoid multiple side effects and be more complicated than just "(a) == (b) and (b) == (c)". (a==b)==c does not expand, only the version without parentheses expands, so you can still do the boolean comparison if you want.
I dunno, I'd expect the right hand expression (2 == false) to be resolved first, then compared to 1.
Compare this with
a = b = c = 5
You evaluate the innermost (i.e. the tail) expression first, and work your way out to the head. As a sexpr it is a little more obvious that way:
(= a (= b (= c 5)))
An argument could easily be made that python is making up special rules for the equality operator by treating the duplication of the operator as merging the expressions into one.
Instead of what you would expect, per the rules of literally every other expression, 5 == 5 == 5:
(== 5 (== 5 5))
It gets rewritten as
(== 5 5 5)
Which one is "unexpected" is really a matter of perspective: are you reading with the rules of English or the rules of your programming language as your context?
I do concede one caveat to this argument: mathematical operators tend to change operator precedence around to avoid making mathematical mistakes. I am mildly of the opinion that this itself was a mistake. Most calculators don't do it, so it's not like anyone would be* that* thrown off by it.
> If you know nothing about Python or coding, and you see a=b=c, you'd think that's true when all three a,b,c are the same.
Sure, that's true if you literally know nothing about coding. But that is not a very common audience for reading code. You only need to spend about 10 minutes coding to realise that the compiler follows fixed rules rather than making an LLM-like guess as to the meaning. If you get that far then most people (admittedly after a bit more 10 minutes) go on to realise that the only way to know what code does is carefully pick it apart step by step, rather than glance at it and guess yourself.
I love Python dearly, but this rule was a misstep on my opinion.
The problem isn’t meaning or intent its inconsistency of operator behavior.
1 + 1 + 1 has different operator behavior then 1 == 1 == 1. The operations here are not consistent and it’s not clear what happens if I overload the operators.
On the surface level python looks more beautiful. But mathematically speaking python is actually more ugly and more inconsistent with weird rules to make things look a certain way.
If you know nothing about Python or coding it is not really relevant as the code is probably read and written more by those who know coding and/or Python?
I know the point of the piece is the python syntax here, but I got stuck on: "judge things like code quality / speed / conciseness etc."
Do people generally write concise code on right off the bat when confronted with a new problem? I've always taken the same approach I would with writing: get something that works; refactor for concision. Maybe everyone else is playing 3D chess and I'm still on Chutes and Ladders?
Yes, even the "unconcise" first draft of anyone who cares enough to be here on HN is much better than some of the code out there.
There's a basic level of concision you'll have by default if you actually understand the problem and the tools. You won't go away out of the way to complicate things. Maybe it's still a far cry from optimal, and definitely not code golf, but enough that I'd be worried if I saw even a first draft without it.
yes, when writing this as other_bool_variable=!bool_var you make it much better because the code speaks for itself ("other_bool_variable is the opposite of bool_variable").
The risk is that you may end with such concise code that nobody understands what it does. I went that dark path with perl 20 years ago, form "oh what a cool language for someone who comes from C" to "let's squeeze that in half a line, and either do no understand what it does, or add three lines of comments to explain".
Do people generally write concise code on right off the bat when confronted with a new problem?
As someone who has been on the interviewer side: this is an indicator of how much the interviewee "thinks first"; the ones who immediately start spewing tons of code are usually those who don't really have a good understanding of what they're trying to do.
As someone who's done a lot of interviews on both sides: I get where you're coming from but don't really think it's as universal as you think. Some people gain understanding through doing; and there's nothing wrong with that?
Thinking first is a very Cartesian approach to development and not necessarily the best way. Many people think through a more engaged approach of doing
I code similarly to how I write, which is similar to how I draw etc. I start with very rough ideas and sketches, then I gradually iterate over those, refining ideas and throwing bits away. And eventually I have a working version/first draft/etc
I just can't get things done if I were to try and think of the entire solution first before getting anything down on paper. Maybe that's an ADHD thing, needing to let the idea take form lest it runs away and I get lost in the weeds
It's less "spewing tons of code" though and more starting with an outline, stub functions and comments to break up the steps before filling those out.
if you know the domain for the task at hand (+a few very basic soft skills) you can learn a lot from watching and discussing with a candidate while they are doing the task.
In reality - tasks like these are garbage leet-code samples; candidated who have been grinding examples rapidly regurgitate the best solution from the forums and impress the interviewer, but would fail if asked a basic question on what they produced. Those that solve it reasonably from first principles fail the task.
There are problems I have encountered a billion times and of course I have an elegant concise solution for it, if I am an experienced programmer.
if any([a in ["-h", "--help"] for a in args]):
display_help()
Would be one of those. Generally I learned that it can be beneficial to treat things as lists early on, e.g. if you have a program that takes one input file it costs you next to nothing to allow the input to be mutiple files as well.
`any` works on arbitrary iterables, so you can use a generator comprehension instead of a list comprehension:
if any((a in ["-h", "--help"] for a in args)):
display_help()
Which algorithmically is a wash, because the time you save not-materializing the list, you waste in the overhead of iterating through a generator (and it's all +/- nanoseconds in recent versions of Python anyway). However, Python has a special syntax that allows you to omit a layer of parentheses when a generator comprehension is the only argument to a function, leaving us with the very elegant (IMO) syntax:
if any(a in ["-h", "--help"] for a in args):
display_help()
This works for any(), all(), str.join(), and others. Yet another demonstration of why I think the iteration protocol is one of the best features in Python (and underappreciated as such).
That still looks like something that a novice might trip over when reading this. Just describing what it does is already a bit convoluted: it tests whether any value of a list of booleans, produced by a generator expression by testing membership of a value in some other list, is True.
I would have gone with the following, as it requires less mental unpacking:
if set(args) & set(["-h", "--help"]):
display_help()
Also, note that any() works with any iterable, the outer brackets are not needed given the list expression.
if any(a in ["-h", "--help"] for a in args):
display_help()
Yeah - when I went through University the mantra I was taught was
1. Get it working
2. Get it working well
If you're faced with an unfamiliar domain, you don't rock up to it and throw all the super optimised fastest code at it. in fact I would view someone who did as having done the test before (making it worthless).
Maybe I misunderstood the piece, but it seems the last sentence, "Well the code is indeed correct" suggests it _did_ work (although I'm also a little confused about what it means when all values are '-' which I interpreted as 'no piece played').
I also judge code quality at the interviews that I perform. I just give the candidates time to improve their code after they come up with something working.
I'm confused about this blog post. Python is mostly C inspired but actually more pseudocode inspired (which helps its popularity) which is why chained expressions exist.
Also, why would you conduct an interview in a language where even if you don't know the syntax (and this is obscure) you could have looked it up or disallowed the interview to be done in Python? I think the due diligence with this issue is more to the interviewer than Python.
> Also, why would you conduct an interview in a language where even if you don't know the syntax (and this is obscure) you could have looked it up or disallowed the interview to be done in Python?
The norm in most of my interviews has been that candidates can solve coding problems in whatever language they are most comfortable with. For most languages like Python etc, it would be a mistake to reject a candidate just because they don't have experience with that specific language.
The norm in most of my interviews has been that candidates can solve coding problems in whatever language they are most comfortable with.
I assume they've already filtered out candidates whose "most comfortable language" isn't the one they're hiring for, or they're going to have a difficult time when they come across the one who wants to use APL or x86 Asm.
Mate, let's be honest, we've all been in interviews where the interviewer had a serious misunderstanding of the language the job was supposedly using, let alone some other language.
In this respect, Python makes a lot more sense since that is how you'd normally write such an equality in math, and generally how people chain them: A=B=C means A=B and B=C.
Part of the problem here is that we treat true/false as "just another value" in programming, and thus the usual operators are used to compare them. In math notation, if you wanted to compare the result of comparison A=B to a Boolean value C, you'd normally write something like (A=B)≡C, using different operators for equality and equivalence.
Interestingly, programming languages haven't always treated Booleans in this way. If you look at ALGOL 60, it starts by cleanly separating expressions into arithmetic and logical ones, and defines completely different sets of operators for each (the reference language also uses = for numeric comparisons and ≡ for Booleans; and boolean inequality is, of course, just XOR).
I think that the main issue here is not treating true/false as values but allowing them to be implicitly converted or compared to numbers with the assumption that true equals 1 and false equals 0.
I think that Rust got this right.
It doesn't allow you to add integer to boolean or multiply boolean by float etc, because it is unclear what does it even mean mathematically.
Also, most languages implicitly assume that any value of any type is either "truthy" or "falsy" thus you can do something like `while(1) { do_stuff() }`. Rust doesn't allow this BS, `if` and `while` expect a boolean so you have to provide a boolean.
That resolves the problem for other types, but you still have the case of (a == b == c) being parsed and evaluated as ((a == b) == c) if all three are booleans, which is still not an intuitive interpretation. I think the PL either has to ban such construct outright and require parentheses, or treat it as an n-ary operator like Python does (albeit perhaps with a few more constraints on what you can chain, for the sake of readability).
A lot of languages have no boolean primitive to begin with. Often in older languages, the values for `true` and `false` are aliases for `0` and `1` respectively. Perl and earlier versions of C, Python, and JavaScript are notable.
Perl is probably the most awkward due to context-sensitive casting. e.g. the string `"0"` in a boolean context evaluates as an integer, and the boolean interpretation for `0` is false.
I don't see how you could interpret "a != b != c" as equivalent to "not (a == b == c)" in the first place. In the first expression a doesn't equal b and b doesn't equal c (no restriction on a and c). In the second expression you could have a == b, but b != c (and vice versa), clearly that's not equivalent to the first expression.
This is a tangent, but checking if all the elements in the diagonal position are the same is not sufficient. You also need to check that any of the elements are not '-'.
Stopped judging candidates on "style" due to stuff like this. I only comment if the code doesn't work, if it works i don't really bother to correct code style or provide feedback on it.
45 minutes interviews aren't really the place to evaluate this.
The blog itself isn't clear, and I think that's confusing some of the people here:
> The candidate this time was programming in python
But the code blocks aren't in python. But in the paragraph afterwards they capitalize "True" which is a python thing. Then afterwards mention they're using javascript as their reference, which uses "true" instead and the code blocks could be javascript.
It feels like the author has more language confusion going on than just this one feature of python.
That's why i put "style" in quotes, the interviewer didn't know about the feature but still decided it was "wrong" or "ugly" even though the code works.
If your knowledge of Python comes from JavaScript, I would not blame Python for it. It's the failure of the person to not "read the instructions" and assume instead. Maybe conduct interviews in languages that you're familiar with?
If you were programming long enough, you easily had contact with dozens of languages. How do you pick up a new language? You can't really treat it as your first one. Especially if you need it "for yesterday". You won't read about "if" or "for". No, you scan for what's different from what you already know. If you're lucky you'll find "python for javascript programmers", but that probably won't go into non-core details like this. In practice, you learn basics first and start coding. Then you find a piece of code somewhere that you don't understand. That's a learning opportunity! However, it's easy if it's a function since it's easily googlable. Operators are harder to search for, for instance in new versions of C# you have operators like "?." (just an example). Since googling is hard you go to some "operators documentation" and try to find it there. And hope that it covers the new version. For cases like this story it's even harder because it describes a concept (chaining) and you maybe don't even know which name is used for that.
At least ChatGPT recognized and explained it correctly, so it makes picking up new features easier than it used to be. I'm making a mental note to ask LLMs whenever I encounter unknown constructs.
If it’s a language I don’t know, I’d still read a book or check the doc for a tour of the syntax. I can scan one in a couple of hours and get an overview I can refer to later for a more specific question. Even if I needed it for yesterday.
This actually gets weirder - in python you can make an arbitrary long comparison operator - it's called comparison chaining - https://www.programiz.com/online-compiler/6uyqb52IVH8if . It works with a lot of operators - https://www.geeksforgeeks.org/chaining-comparison-operators-...
Once you know how it works and are used to it, I think it makes the code easier to parse... but there are heavy downsides. For example, it's not clear how short circuiting works. I've used python a bunch and logically I expect ```side_effect3()``` to not be evaluated if results of 1 and 2 are not equal: ```side_effect1() == side_effect2() == side_effect3()```. However, I do not know that for sure, while in other languages I would be able to reason about this from basic principles.
I think most people would expect expressions like `5 < x < 10` to work as they do in math, without necessarily thinking about it in terms of being an overloaded binary operator. The result in other languages that `5 < 12 < 10` = `true < 10` = `true` is more surprising, just that we've (perhaps by being bitten by it early on) gotten used to it.
It is clear if you read the docs.
https://docs.python.org/3/reference/expressions.html#compari...
https://docs.python.org/3/reference/expressions.html#boolean...
Example
(a==b)==c would be different from a==b==c
Opinion: Code that depends on side effects like that in a conditional is BAD. It is not an optimization, it's an obfuscation.
There's a lot of options for a language to deal with a statement like this. It could be a syntax error, a compile or lint warning, it could work by doing order of operations and comparing the boolean from one compare with the third element, or it could work in the way you described.
I'd prefer languages that I work with to chose earlier options from that list. In most languages this sort of statement is usually a bug, and deserves explicit parenthesis to clarify the order of operations. I really don't want to have to pull out an operator precedence chart in order to read your code, much less have to look up some esoterica about this specific language.
Hard disagree here. I write code for people who can read the language. This includes operator precedence.
Python 'won' mostly because non-programmers could look at it, more or less understand what was going on, and feel that this was something they probably could learn.
"There are no children here at the 4H club either! Am I so out of touch?
No... it's the children who are wrong."
-Principal Skinner, "The Simpsons"
EDIT to clarify- I'm just being silly, not suggesting anyone is right or wrong here.
But then is the mathematical '=' really a binary operator that maps two numerical inputs to a boolean output? It feels then as an abuse of notation if (A=B)=C doesn't allow A=B to change type?
Because I don't really have much use for a symbol that is some times doing a binary mapping from numbers to booleans and some times becomes some sort of ternary operator which maps 3 inputs to one boolean.
Compare this with
You evaluate the innermost (i.e. the tail) expression first, and work your way out to the head. As a sexpr it is a little more obvious that way: An argument could easily be made that python is making up special rules for the equality operator by treating the duplication of the operator as merging the expressions into one.Instead of what you would expect, per the rules of literally every other expression, 5 == 5 == 5:
It gets rewritten as Which one is "unexpected" is really a matter of perspective: are you reading with the rules of English or the rules of your programming language as your context?I do concede one caveat to this argument: mathematical operators tend to change operator precedence around to avoid making mathematical mistakes. I am mildly of the opinion that this itself was a mistake. Most calculators don't do it, so it's not like anyone would be* that* thrown off by it.
Sure, that's true if you literally know nothing about coding. But that is not a very common audience for reading code. You only need to spend about 10 minutes coding to realise that the compiler follows fixed rules rather than making an LLM-like guess as to the meaning. If you get that far then most people (admittedly after a bit more 10 minutes) go on to realise that the only way to know what code does is carefully pick it apart step by step, rather than glance at it and guess yourself.
I love Python dearly, but this rule was a misstep on my opinion.
1 + 1 + 1 has different operator behavior then 1 == 1 == 1. The operations here are not consistent and it’s not clear what happens if I overload the operators.
On the surface level python looks more beautiful. But mathematically speaking python is actually more ugly and more inconsistent with weird rules to make things look a certain way.
Do people generally write concise code on right off the bat when confronted with a new problem? I've always taken the same approach I would with writing: get something that works; refactor for concision. Maybe everyone else is playing 3D chess and I'm still on Chutes and Ladders?
I've seen real code that looks like
Which I suppose could be called anti-concise.There's a basic level of concision you'll have by default if you actually understand the problem and the tools. You won't go away out of the way to complicate things. Maybe it's still a far cry from optimal, and definitely not code golf, but enough that I'd be worried if I saw even a first draft without it.
The risk is that you may end with such concise code that nobody understands what it does. I went that dark path with perl 20 years ago, form "oh what a cool language for someone who comes from C" to "let's squeeze that in half a line, and either do no understand what it does, or add three lines of comments to explain".
But yes, there is a good middle-groud somewhere
Deleted Comment
As someone who has been on the interviewer side: this is an indicator of how much the interviewee "thinks first"; the ones who immediately start spewing tons of code are usually those who don't really have a good understanding of what they're trying to do.
I code similarly to how I write, which is similar to how I draw etc. I start with very rough ideas and sketches, then I gradually iterate over those, refining ideas and throwing bits away. And eventually I have a working version/first draft/etc
I just can't get things done if I were to try and think of the entire solution first before getting anything down on paper. Maybe that's an ADHD thing, needing to let the idea take form lest it runs away and I get lost in the weeds
It's less "spewing tons of code" though and more starting with an outline, stub functions and comments to break up the steps before filling those out.
Think first, code second.
In reality - tasks like these are garbage leet-code samples; candidated who have been grinding examples rapidly regurgitate the best solution from the forums and impress the interviewer, but would fail if asked a basic question on what they produced. Those that solve it reasonably from first principles fail the task.
`any` works on arbitrary iterables, so you can use a generator comprehension instead of a list comprehension:
Which algorithmically is a wash, because the time you save not-materializing the list, you waste in the overhead of iterating through a generator (and it's all +/- nanoseconds in recent versions of Python anyway). However, Python has a special syntax that allows you to omit a layer of parentheses when a generator comprehension is the only argument to a function, leaving us with the very elegant (IMO) syntax: This works for any(), all(), str.join(), and others. Yet another demonstration of why I think the iteration protocol is one of the best features in Python (and underappreciated as such).I would have gone with the following, as it requires less mental unpacking:
Also, note that any() works with any iterable, the outer brackets are not needed given the list expression.1. Get it working
2. Get it working well
If you're faced with an unfamiliar domain, you don't rock up to it and throw all the super optimised fastest code at it. in fact I would view someone who did as having done the test before (making it worthless).
Syntactical sugar doesn’t matter anymore at that stage
Dead Comment
Also, why would you conduct an interview in a language where even if you don't know the syntax (and this is obscure) you could have looked it up or disallowed the interview to be done in Python? I think the due diligence with this issue is more to the interviewer than Python.
The norm in most of my interviews has been that candidates can solve coding problems in whatever language they are most comfortable with. For most languages like Python etc, it would be a mistake to reject a candidate just because they don't have experience with that specific language.
I assume they've already filtered out candidates whose "most comfortable language" isn't the one they're hiring for, or they're going to have a difficult time when they come across the one who wants to use APL or x86 Asm.
Part of the problem here is that we treat true/false as "just another value" in programming, and thus the usual operators are used to compare them. In math notation, if you wanted to compare the result of comparison A=B to a Boolean value C, you'd normally write something like (A=B)≡C, using different operators for equality and equivalence.
Interestingly, programming languages haven't always treated Booleans in this way. If you look at ALGOL 60, it starts by cleanly separating expressions into arithmetic and logical ones, and defines completely different sets of operators for each (the reference language also uses = for numeric comparisons and ≡ for Booleans; and boolean inequality is, of course, just XOR).
I think that Rust got this right. It doesn't allow you to add integer to boolean or multiply boolean by float etc, because it is unclear what does it even mean mathematically.
Also, most languages implicitly assume that any value of any type is either "truthy" or "falsy" thus you can do something like `while(1) { do_stuff() }`. Rust doesn't allow this BS, `if` and `while` expect a boolean so you have to provide a boolean.
Perl is probably the most awkward due to context-sensitive casting. e.g. the string `"0"` in a boolean context evaluates as an integer, and the boolean interpretation for `0` is false.
I cannot see it for !=
"a != b != c" is not equal to "not (a == b == c)" which is a bit strange imo
The code is incorrect: if all entries are '-' there is no winner. Even ignoring the braces…
Deleted Comment
Deleted Comment
45 minutes interviews aren't really the place to evaluate this.
(But, the intent on the interviewee's part was definitely clear - I'd definitely let this pass.)
> The candidate this time was programming in python
But the code blocks aren't in python. But in the paragraph afterwards they capitalize "True" which is a python thing. Then afterwards mention they're using javascript as their reference, which uses "true" instead and the code blocks could be javascript.
It feels like the author has more language confusion going on than just this one feature of python.
You need to detect if someone is humble and smart enough to fit in to how you do things as a team, whatever that is.
Not reject someone for not hitting a style guide on a piece of paper tucked behind a hidden secret door. (As I was once!)
At least ChatGPT recognized and explained it correctly, so it makes picking up new features easier than it used to be. I'm making a mental note to ask LLMs whenever I encounter unknown constructs.
You're probably thinking `1 === 1 === 1` which is indeed false
[0] https://github.com/satwikkansal/wtfpython?tab=readme-ov-file...