Yoda conditions
en.wikipedia.org
en.wikipedia.org
There are zero reasons to use that ugly and unnatural Yoda notation in 2019.
Specifically, it's both (1) easy to make a typo where you meant "==" but typed "=" and (2) not easy to visually distinguish the two.
Java for example expects a bool for for if(). if(a=b) is only valid when they are, which is relatively rare. So it is mostly a compile error.
If C didn't allow assignments to be used as expressions, then it wouldn't be an issue.
Chained assignment isn't the only way to use an assignment within an expression, so I think it's still more accurate not to say chained assignment as the cause, but chained assignment might have been a big part of the motion for making assignments expressions instead of just statements.
if (systemcall(“some string”, expression_argument(args), SC_MODE_1 | SC_MODE_DEFAULT) != 0)
if (0 != systemcall(“some string”, expression_argument(args), SC_MODE_1 | SC_MODE_DEFAULT))
One may find it easier to read/navigate flow control in C code that returns status codes, when these codes are stated beforehand. When there are series of long lines and a mix of 0==success and 0==false, it is easy to get lost, at least in my experience. int result = systemcall(“some string”, expression_argument(args), SC_MODE_1 | SC_MODE_DEFAULT))
if (result != 0)
Separation of concerns. Each line does one thing. Making a system call and branching on its result are two separate tasks.Did you compare writing in different ways and reading it later? This specific style confuses me the most, because I have to mentally connect an assignment with a flow control, because there is no guarantee that a following if() will not use a different variable or it was not shadowed, misnamed, etc. And given that, at once I see x2-3 lines less than usual.
In the past, I've done things like this:
#define TRY(exp) \
do { \
int TRY_result = exp; \
if (TRY_result != 0) \
return TRY_result; \
} while (0)
And then instead of the above I can just write: TRY(systemcall(“some string”, expression_argument(args), SC_MODE_1 | SC_MODE_DEFAULT)));
(Sometimes, one wants more complex error detection than just comparison to zero, or more complex error handling than just returning the result code. Often, it is possible to build a more complex version of the above TRY macro to meet those specific requirements.) while ((status = systemcall(...)) != SUCCESS) {
do something with status;
}
Leave a comment there explaining the assignment.Likewise:
if ((status = systemcall(...)) != SUCCESS) goto error;
Or something like that. I don't understand the hate, just make sure it's obvious what you're doing. while(status != SUCCESS) {
status = syscall(...);
// do something with status
} while (1) {
int ret = ...;
if (ret == ...) break;
if (ret == some_other_condition) break;
// additional termination conditions....
// do exactly one thing
} int c;
while((c = getopt(argc, argv, argstr)) >= 0) {...}
I’d say it’s acceptable, and Python is adding syntax so it can mix assignments and checks.But enabling it on the language level brings a large amount of risk and complexity just for what amounts to a micro-optimization.
Please don't. This is a perfectly understandable idiom in C. Don't explain the language in your comments; that's the job of a text book, which should be read by anyone before they read your code.
"Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems."
It also reduces time in debugging as you catch it immediately.
It's my favourite way of writing if statements.
(And then Python came along and was like, "U wot mate?", and there were had little wars over "syntactically-significant indentation" and tabs vs. spaces, and how many spaces... And on, and on...)
I got lucky I think. The first time I saw "yoda" conditions I had an abreaction. But then I realized the programmer who wrote it was "a foreigner, with ways different than our own." That somehow made it alright again.
Virginia Satir said that people would choose the familiar even over death. She said the strongest human drive is for the familiar.
Jef Raskin pointed out that the way we use the word "intuitive" it really means "familiar". The first time he handed a mouse to someone to try, who hadn't previously seen it in use, she turned it over and used it like a little trackball.
I'm pretty sure the only reason we don't all use Lisp is just human drive for what's already familiar.
Both sides of the equality check are equally important.
I've actually noticed one thing about Common Core is that they do try much harder than when I was a kid to not accidentally create that impression; my kid's homework is full of "4 + 3 = ___" followed immediately by "____ = 8 + 2". Still kind of glossing over "=" as a "simplify" operator, but it's still an improvement over when I was a kid when it was really easy to pick up the idea that the "=" operator was actually a function meaning "take the expression on the left and simplify it". (I mean, there's a sense where such students aren't even wrong; it is the rational conclusion from the evidence presented.)
What? No. De '=' sign is very simple: what's written to the left of it is equal to what's written on its right.
Outside of math it probably doesn't matter. Inside of math it matters a lot. Programming is math.
And I was solving systems of equations with substitution and doing all the usual things to equations one does in math classes, and to all external appearances I would have looked like I understood equality prior to that. But I didn't. Not fully. In hindsight, the best evidence of this was in physics class, where I was perfectly adequate at taking the provided equations and using them, and I understood their derivation, but I still wasn't all that great at combining them fluidly; F = ma, yes, but it still didn't quite fully register that that meant anywhere I saw an F, in any other equation in which it appeared, I could drop in an ma. It's not that F is "convertable" to ma, or that ma simplifies to F, or that the name of ma is F... it's that they ARE each other, there is no conceivable way you can separate them, there is no conceivably witnessable in any way, mathematically or practically, effect from substituting one for the other, there is only one entity that can be named in various ways. I did not fluidly combine them the way I should. If I had the class to do over again, I would do way better this time, even we could somehow erase my first pass entirely from my mind.
Code is for humans, not computers. The order I choose when writing an expression is driven by what I want to convey to the next person reading my code.
`if (button.state == .enabled)` suggests to the reader that the button’s state is the thing we’re interested in in this particular piece of code. Reversing the operands is confusing not because I don’t understand the communicative property, it’s confusing because it puts the emphasis on the wrong thing: I’m not checking if the enabled state matches an expectation, I’m checking if the button’s state matches an expectation. And I want the reader to understand that.
x == y
aloud as an English statement:> X equals Y
> X and Y are equal
The first sentence uses active voice - the second passive. Active voice ascribes agency to X; It makes a subject/object distinction between X and Y. Passive voice makes them both objects.
When reading code, it makes more sense to ascribe agency to variables than to constants, which is why people are more comfortable reading x == 0 than 0 == x. Zero can’t change, so it can’t do anything to make itself equal something. X can change, so it is capable of equaling things.
If you’re equally comfortable with either formulation, maybe you just prefer the passive reading of the sentence.
But I do wonder if you’d be equally happy if I went through your codebase and replaced every for loop condition from i < length to length > i...
"But I do wonder if you’d be equally happy if I went through your codebase and replaced every for loop condition from i < length to length > i..."
You'd have a hard time of it; I'm a vigorous believer in doing whatever you can in the language you're in to avoid the old 3-element-style C-style for loops in favor of "for ... in ..." or local equivalent. I'm pretty sure you'd get single digit hits for C-style for loops with multiple clauses in the condition.
assert ('Content-Type', 'text/plain') in dupefail.headers
assert b"registration denied" in dupefail.body
assert "403 Forbidden" == dupefail.status
I also happen to pay attention to the linter fart^Woutput: C: 61,11: Comparison should be dupefail.status == '403 Forbidden' (misplaced-comparison-constant)
I totally admit my thorough hate of pylint, it hasn't really ever helped me once -- mostly led to more `#pylint: disable=…` garbage in the source. I still force myself to use it, as a duty by fellow... other engineers. But that's an aside.Please, tell me how much more "natural" and "un-ugly" the 3-line snippet would read to you had it the third line assert flipped away from Yoda style, just as pylint suggests. I'm eager to hear you.
To properly support assert with good error reporting, pytest has to rewrite the byte code. See http://doc.pytest.org/en/latest/assert.html#assert-details
On the contrary; there're many common contexts where Yoda comparisons looks more "natural", meaning they avoid breaking the surrounding code flow, and bring the important part (the constant) up-front. I even brought up a real-world example. Having read `assert "403 Forbidden" == ` and remembering the context, can't you already guess the RHS (and just skim over it)? Sure you can. Non-yoda loses here.
Be aware: you don't have to take a "for/against" side in this debate, as our buggy brains try to in every flame war. Both sides have a point. Familiarize yourself, and decide on case-by-case basis.
While we're at it: self.assertEqual() is super-ugly and unnatural, in my judgement. Why am I forced to use _thrice_ as much words to express the simple single-word concept of an assert? Why can't I spare the extra pair of parens, and spell == directly? I see nothing wrong with bytecode rewriting; it's amazing they can do it, and I appreciate the effort.
Treating the cause: Use static analysis.
In Clojure for example: (= ...) just tests for (actual) equality, while (def ...) assigns a symbol to a value, or (swap! atom f) changes the value of an atom (reference type).
Treating the cause's cause's cause: design your own programming language to be a strongly typed functional or logic programming language that is a descendant of the ideas in XSLT, where the attitude was “the most common thing you do in programming is to map one data structure into another, so let’s build the whole language around making those mappings easy first.” There can now be no confusion.
Probably treating the cause's cause's cause's cause is something like “just use Excel for everything, why are you using these other languages?”
It's be harder to get things done, but we'd be more certain about them
if( booleanVariable == true ) ...
This was also the place that ordered me not to use LINQ statements because, and I quote, "you need to write code that someone fresh out of high school could understand". I don't work there any more. if( nullableBooleanVariable ?? false )
was also forbidden. "foo".equals(myVar)
instead of writing: myVar.equals("foo")
which could can cause NullPointerException if myVar is null.$x++ if (!condition);
like if you start to increment $x++ and then you realize wait a second I only mean if....
"unless" likewise functions as an "if not".
This is such a "natural" way to write.
Let's compare what happens if you have the thought to add a "statement modifier" in any other programmer language.
you're written your statement, and now you have to go back to the beginning of your line, write your condition, open a brace, go back to the end of the line, and close your brace. Or maybe begin an entire code block, since you can't have it on one line anymore.
it is so much less natural. why can't other programming languages have that?
def do_stuff()
return unless stuff_enabled?
...
end x += 1 if !condition
Better as x += 1 unless condition
I use them not as an afterthought but by design. This is the Ruby Style Guide about it https://rubystyle.guide/#if-as-a-modifierAlthough the "unless" case very often trips me. Somehow my brain can't parse that correctly and I've found that surprisingly many work colleagues have the same problem.
Be careful what you wish for...
Is there a shorter way of saying, "the wrong solution to the right problem"?
More often than not, the first solution to a problem isn't the best, but the best solution would take a lot of tooling fixes.
The Yoda condition trick is a relic of the past, well past its usefulness and best left in its grave alongside Hungarian notation, macros for "inlining", #include "blah.c", goto, etc.
p̶o̶r̶t̶a̶l̶s̶
I've had a lot of feedback from colleagues that it's harder to read because it's rare. If my colleagues say it's hard to read, that's good enough for me.
EDIT: to be fair, this sentence was added today [1]. Not sure if it would have been there when I opened the tab earlier.
[1] https://en.wikipedia.org/w/index.php?title=Yoda_conditions&t...