Settings

Theme

Yoda Conditions

en.wikipedia.org

76 points by sloankev 9 years ago · 82 comments

Reader

kibwen 9 years ago

One of the best bugs I've ever seen was in the game Dungeon Crawl Stone Soup. It's a roguelike, and a venerable old one at that, so the codebase is quite tangled and convoluted, with logic for everything in every corner. One of the worshippable gods in the game is Xom, god of chaos, whose effects are just as likely to kill their worshipers as to help them (frustrating for those who might actually want to win, but always good for a laugh). And somewhere in the main game loop was some bit of logic that was only ever supposed to activate while the player was actively worshiping Xom. Guess how this was accidentally implemented:

  if (you.religion = GOD_XOM) {
This actually made it to the live test server, where player save files are automatically upgraded to the most recent build, so anyone logging in immediately found themselves infallibly worshiping the god of chaos, every single tick of in-game time (renouncement is futile). Chaos indeed!
paol 9 years ago

Don't do this.

In languages where accidental assignment is possible (i.e. writing if(a=b) when you meant if(a==b) ), configure the compiler or linter to emit a warning in this situation.

For example in C, GCC will complain about this when compiling with -Wall, which you should be using anyway.

  • hashkb 9 years ago

    The real lesson is for language designers, who for some reason love using equals for assignment. Lisp got it right a hundred years ago and nobody learned.

    • dom96 9 years ago

      There is nothing wrong with using equals for assignment, the language designers just have to disallow assignments in if conditions.

      • tel 9 years ago

        It's a little weird. lvalues and rvalues are different things so a biased symbol feels nice to me, <-, e.g.

      • junke 9 years ago

        One more corner case, instead of designing orthogonal features.

        • kibwen 9 years ago

          It's not a corner case if you do it right.

          For example, in C the assignment operator evaluates to the value of the right-hand side expression (so `int x = (y = 2);` initializes `x` to 2). But in Rust, the assignment operator always evaluates to `()` (a.k.a. "unit", the empty tuple), and trust me, this is how you want the language to work in the presence of pervasive move semantics and single ownership. Furthermore, nothing in Rust is "truthy": if-expressions accept a boolean and nothing else (this is also, IMO, exactly what you want in every language, but I'm sure others will disagree :P ). So `if x = 2 {` is a compiler error in Rust because `()` can't be coerced to a boolean (to say nothing of the fact that this reassignment would probably be a compiler error anyway because most variables in Rust are immutable). This doesn't require any acrobatics, it's just the natural fallout of how the rest of the language works.

      • kazinator 9 years ago

        That's a cure worse than the disease. If I faced a false choice between = as an assignment operator or else accepting syntax that distinguishes statements and expressions, I'd just take the = operator.

      • nilved 9 years ago

        There's nothing wrong with using assignment in if conditions. For example, `if let Some(thing) = computation_that_may_fail { ... }`.

        • kazinator 9 years ago

          The operator there is let. The = is just a punctuator which indicates the initial value expression for the let.

          Litmus test: if this was rendered into S-expressions, the let would stay, but the = would disappear.

          • nilved 9 years ago

            No, that's just because the example I used was Rust. In Ruby it would be `if user = User.find(..)` and the same goes for Go and plenty of other languages.

            • kazinator 9 years ago

              If user is being freshly bound, that isn't an assignment but an initialization.

              If the construct is not allowed when user is already in scope, it reflects the designer's view that an assignment in a conditional isn't a good thing.

              If the construct is allowed when user exists already, with no diagnostic, then it carries the pitfalls that this thread is about.

    • cronjobber 9 years ago

      Hacker News is implemented in a dialect of Lisp designed by a guy who wrote multiple books on Lisp. Seems even lispers don't learn:

      > The assignment operator is =. I was dubious about this, but decided to try it and see if I got used to it. It turns out to work well, even in prefix. Stripes stand out, which is why they get used on warning signs and poisonous animals.

      http://www.paulgraham.com/arcll1.html

      • kazinator 9 years ago

        That is quite silly. I briefly contemplated this idea for TXR Lisp, but completely rejected it. The = function is numeric comparison, like in Common Lisp. You don't want to turn that into assignment. People used to Lisp dialects where = is numeric comparison will make the mistake:

          (if (= whatever whatever-else) (you-got-burned))
        
        Arc code written by people used to Lisp dialects where = is a pure comparison function, or Arc code converted from other Lisp dialects, has to be carefully reviewed against this.

        If I put this into a Lisp dialect, I would make the code walker issue a warning whenever the value of a (= ...) assignment is used, and provide an alternative assignment operator which doesn't have that warning.

    • flukus 9 years ago

      I'd say the issue is keyboard designers, if ':' didn't require shift I bet we'd see it for assignment more often.

    • marcosdumay 9 years ago

      Well, languages can either use another assignment operator or have type system that wont check that kind of statement. Both ways fix it.

      In fact, it's not easy to have this kind of bug on your language. C just has it because people wanted to write stuff like `int a = b = 0`.

      • jordigh 9 years ago

        `int a <- b <- 0` or `int a := b := 0` would have worked just as well.

        Programmers using equals for assignment is a bit of a misunderstanding of what the mathematical idiom "let x = 5" means. The assignment is signaled by "let", not by "=". In fact, several programming languages use "let" exactly for this purpose too.

  • kbenson 9 years ago

    There are useful cases for this. It's much less ambiguous in those cases if your language allows the definition of the variable in the same location, and scopes it. For example, Perl:

        use strict;
        use warnings;
        
        sub one { 1 }
        
        if ( my $one = one() ) {
            say $one; # prints 1
        }
        
        say $one; # compilation error, "Global symbol "$one" requires explicit package name"
    
    This case is trivial, but when you want a temporary variable and don't want to clutter your scope, it can be useful.

    That's not to say assignment and equivalence might not be better off with different operators, as noted by others here.

    • dbaupp 9 years ago

      Declaration and a (mutating) assignment are different: your example demonstrates that Perl signals the declaration with `my`, and other languages offer similar things like `if let ... = ... { ... }`

      • kbenson 9 years ago

        > Declaration and a (mutating) assignment are different

        Yes, that's what I was trying to get at by saying definition, but not very clearly. It's one of the reasons I prefer languages to require and clearly indicate variable declaration in most cases.

  • brianwawok 9 years ago

    Eh, I think it is good in Java in cases where Null input is possible.

    • paol 9 years ago

      Yes, "foo".equals(bar) is useful in Java if bar can be null, but that's a different thing.

    • megawatthours 9 years ago

      I never got the Java argument. I would much rather fail early and noisily with an NPE than return false and let my program happily chug along when it wasn't expecting a null. If the value is intended to be nullable, I would use an optional.

      • brianwawok 9 years ago

        Well for 95% of Java's live, there was no such thing as Optional.

        So say untrusted user input.. many cases where it could be something or null. Yoda checking it was a nice way to save a null check.

      • xxs 9 years ago

        Java8 optional is way slower (write a tree alike struct with optional for left/right/parent) and null checks are one of the free ones. (Hardware optimized)

    • PaulHoule 9 years ago

      I think it is better than if(x!=null && x.equals("y")

      You have to take bits of concision where you can find them in Java.

  • busterarm 9 years ago

    Counterargument:

    Do do this if the coding standards for the language/framework you're working in require it. Like WordPress.

    • sethrin 9 years ago

      Wordpress is a legacy procedural codebase and should not be used as an example of good development practices.

      • busterarm 9 years ago

        Regardless of your religious convictions, Wordpress still exists and that work has to be done by somebody.

        I'd rather it be done by people who care than those that don't.

        • sethrin 9 years ago

          How is that a comment on coding standards or best practices? I'm afraid the arguments against procedural code are not religious. Procedural code provides poor encapsulation and modularity. If procedural code was all that great, we wouldn't have needed to have C++, C#, Java, and Objective-C, and any number of other derivative languages. Regardless of my or your opinions, WordPress is a legacy codebase. Procedural code today is all but completely discarded.

  • michaelmior 9 years ago

    While I don't disagree, curious to hear your reasoning for not doing this.

    • paol 9 years ago

      This trick can only save people who remember to use it. Warnings emitted by tooling save everyone.

      Also it's slightly awkward to read, as mentioned by others already.

      • yellowapple 9 years ago

        Warnings can only save people who bother to read them. Yoda conditionals save everyone trying to run or compile the code in question. It goes both ways ;)

        Re: awkwardness, it doesn't feel awkward at all to me; given that Erlang and Elixir use assignment for destructuring and pattern-matching, Yoda conditionals feel very natural to me in comparison.

        For example, this is valid Elixir code:

            foo = {1, 2}
            
            bar = if {baz, 2} = foo do
                    525600
                  end
            
            IO.inspect foo  # prints {1,2}
            IO.inspect bar  # prints 525600
            IO.inspect baz  # prints 1
      • busterarm 9 years ago

        The trick saves the person writing the code and anyone modifying it down the road.

        You can't force everyone who edits your code to have linters and warnings turned on. Not everyone follows bests practices.

        • michaelmior 9 years ago

          Does it? You also can't force everyone who edits your code to follow your conventions.

          • digler999 9 years ago

            actually you can, depending on the management structure in an organization :)

    • mikeash 9 years ago

      It's unnatural, confusing to read, and won't save you anyway in cases where you're comparing two variables.

      It's OK if your tools absolutely can't diagnose accidental if(a = b), but it should be the last resort.

      • akerro 9 years ago

        In some languages it's better to use it, eg. in Java you can go with:

           mString != null && mString.equals("test")
        
        or

           "test".equals(mString)
        
        They do the same, but the second one adds hidden `defensive programming` to prevent NPE in `equals`. Saves time AND code.

        Intended usage of if(a=b) should be written as if((a=b)). Don't know what tool you use at work or at home, but most static code analysers will point you that. Try SonarQube at least.

      • scj 9 years ago

        > It's unnatural, confusing to read ...

        I feel as if that can rephrased "Don't do it because it is not done." A left-hand side constant is a convention that can have practical benefits in limited cases. Which at least should merit consideration.

        • mikeash 9 years ago

          "Don't do it because it is not done" is a good reason unless you're working solo and are sure your code will never be seen by others. And even then, it's a good idea to stick to what's done so you don't accumulate bad habits for projects that do have others looking at the code.

      • pasquinelli 9 years ago

        how would you write a yoda conditional with two variables? which is the yoda conditional (a == b), or (b == a)?

        • mikeash 9 years ago

          You can't if they're both mutable, since the whole idea of a yoda conditional is to put an immutable thing on the left side. What I meant was that you can't use them in that case, so it's a technique you can't use with 100% consistency.

  • MikkoFinell 9 years ago

    Also clang warns "using the result of an assignment as a condition without parentheses".

  • mirekrusin 9 years ago

    Exactly, and use 'if ((a = b)) ...' in those rare cases when it's appropriate.

    • efaref 9 years ago

      Even then I would prefer an explicit comparison:

          if ((a = b) != NULL) ...
      
      I think Rust has a happy medium, where assignment evaluates to (), not the variable (important as this means you don't have an implicit borrow), but the idiom expressed here is usually replaced by:

          if let Some(thing) = fn_that_returns_option_thing() ...
  • devy 9 years ago

    What about PHP? It's in the coding standard in WordPress and Symfony... :(

thefifthsetpin 9 years ago

I'm fond of Yoda conditionals, but not for the commonly cited reasons. Mostly I just like that they promote the interesting/unpredictable part of the comparison to the front. I consider the safety from unexpected assignment, from null pointer exceptions, and the Star Wars reference all to be minor perks.

Take this example:

  function handle_request($http){
    if("OPTIONS" === $http.request.method){
      ...
    }else if("GET" === $http.request.method){
       ...
    }else if("PUT" === $http.request.method){
      ...
    }else if($http.request.method === "POST"){
      ...
    }
    ...
  }
Did you read $http.request.method four times? I bet not. Some of you may have read it the first time; I wouldn't have. But to know that the fourth block handled POST requests I made you read to the end of the line. That makes it harder to scan the code looking for the section that you want to change. In production code that I've seen, the predictable side of a comparison is often quite long.

What's especially bizarre to me is the reason people give when advocating writing in the last style. Yes, it verbalizes nicely as "Otherwise, if the http request method post, then..." But who internally translates their code to English to understand it? COBOL was designed to read like natural language. Do you enjoy programming in COBOL? If English-like syntax is desirable for code readability, why don't modern programming languages prioritize English-like syntax, too?

Greek mathematics got stuck because it remained a verbal, pictorial activity, Moslem "algebra", after a timid attempt at symbolism, died when it returned to the rhetoric style, and the modern civilized world could only emerge —for better or for worse— ... thanks to the carefully, or at least consciously designed formal symbolisms that we owe to people like Vieta, Descartes, Leibniz, and (later) Boole.

-- Dijkstra,

selfsimilar 9 years ago

In PHP I like using Yoda Conditions because there's a common idiom of testing assignment in the conditional:

    if ($value = getSomeValue()) {
      // Safely use value
    }
Yoda Conditions defend nicely against accidents when '=' and '==' can be used legally this way and honestly you get used to reading them pretty quick.
  • YSFEJ4SWJUVU6 9 years ago

    Just wrap the condition inside another pair of parentheses; that should get any linter off your back without needing to deal with the annoying flipped conditions – and if someone doesn't know the code base has a 100% coverage of Yoda conditions, they might just think there's a bug on the line when seeing it for the first time (unless you add an explanatory comment each time, at which point the assignment inside the condition has bought you nothing, as you might just lift it on a row of its own before the if statement).

    C++, for instance however has language syntax to prevent confusion when using this idiom – declarations inside conditionals:

      if (auto val = getval())
        foo(val); // executed only if getval() returned something that evaluates to true
                  // in a boolean context
    
    Considering that PHP already has the useless var keyword, they might just adopt something similar in the future

      if (var $val = getval()) {}
  • efaref 9 years ago

    I've been coding for decades and I still find them jarring.

    They're also a false sense of security as they won't catch:

        if $(value = $someOthervalue) ...
  • ghurtado 9 years ago

    I don't see at all how you could call this a Yoda Condition: it couldn't be written any other way, as it would cause a syntax error.

namanyayg 9 years ago

A lot more of similar programming jargon by Jeff Atwood [1]

[1] https://blog.codinghorror.com/new-programming-jargon/

logfromblammo 9 years ago

I was once officially reprimanded for using Yoda conditionals. That was at the same place that required this:

  if( booleanVariable == true )
  {
      ...
  }
  else if( booleanVariable == false )
  {
      ...
  }
  else
  {
      // Typically, this section would include an exact copy
      // of one of the above sections, as it was 3 years ago,
      // presumably to pad out the SLoC metrics.
  }
That place had its head so far up its own ass....

Equivalence is commutative. Please never complain about the order of its operands as "confusing" or "less readable". It just tells me that you're an ass, trying to enforce a coding convention that has no objective reason to exist.

geofft 9 years ago

I ran across a bug at $dayjob where someone was clearly trying to use Yoda conditions, but messed up differently:

    if "start" == argv[1]:
        ....
    else if "stop" == argv[1]:
        ....
    else if "reload":
        ....
    else if "status" == argv[1]:
        ....
I think it'd be harder to make this mistake with the conditions the right way around. (Also, never mind that the equality bug isn't even possible in Python.)
  • jquast 9 years ago

    I wish python had case statements for this very problem. If the statement blocks can be defined by a common function signature, your example could use

      {'start':  fn_start,
       'stop': fn_stop,
      }.get(
        argv[1] if len(argv) > 1 else None,
        fn_undefined
      )(*args, **kwargs)
    
    I used this in a demonstration terminal nibbles/worms video game clone, https://github.com/jquast/blessed/blob/master/bin/worms.py#L...
koolba 9 years ago

A similar but arguably more useful version of this is when you have two variables, one of which may be null. Traditionally you issue an if-condition test using the variable user input as the left operand but switching it handles the null case automatically.

Ex:

    Foo SOME_CONSTANT = <something-not-null>;

    Foo userSuppliedInput = <some-user-input-possibly-null>;

    // This can raise an null pointer exception
    if (userSuppliedInput.isEquals(SOME_CONSTANT) {
        // ...
    }

    // This can't raise a null pointer exception (assuming isEquals handles nulls properly):
    if (SOME_CONSTANT.isEquals(userSuppliedInput) {
        // ...
    }
  • mikeash 9 years ago

    Objective-C is really fun here. Messages to nil are no-ops which return zero, which for booleans results in false. Thus, the conditional works just fine either way:

      if([userSuppliedInput isEqual: SOME_CONSTANT]) { ...
    
    If userSuppliedInput is nil, the isEqual: returns false, and it works.

    It gets really fun when both might be nil:

      if([a isEqual: b]) { ...
    
    If a and b are both nil, then they're conceptually equal, but isEqual: still returns false because it's a mindless "always return 0 for messages to nil" thing that doesn't even look at any parameters passed in.
    • efaref 9 years ago

      Not sure I like the sound of that. It sounds like the floating point wat of "NaN != NaN".

      • koolba 9 years ago

        Or the SQL classic:

            NULL = NULL
        
        It's not true or false, ... it's NULL!
      • mikeash 9 years ago

        Making messages to nil be a no-op is certainly an "interesting" feature of Objective-C. It can make some code a lot more convenient to write, but it can also make certain bugs really difficult to track down.

        After having done Swift for a while, I'm a big fan of using an option type so that "reference to object" is a different static type altogether from "reference to object, or nil."

        • efaref 9 years ago

          Yes, I really like Rust and the way its Option<Box<T>> decays to a nullable pointer.

nayuki 9 years ago

I would say that the phrase "Yoda conditions" more appropriately describes Ruby's alternative form of if-statements:

    if x > 0:
        y += x
versus

    y += x if x > 0
  • kentt 9 years ago

    One of the worst parts of Ruby. I can see the argument that it could be better to have

      save if valid
    
    rather than

      if valid
        save
      end
    
    but what I see 9 times out of 10 is

      things.do each |thing|
        foo
        bar
        baz
      end if valid
    
    or

    really.long.thing.that.i.try.to.parse.in.my.head if acutally_almost_never_happens

    which is harder to read since I read top to bottom / left to right, but the flow is bottom to top and right to left

    • kbenson 9 years ago

      This is something Ruby likely got form Perl,but they cribbed it wrong. Perl's post-conditionals only work on single statements, not blocks. You can use them for functional style statements though. I.e.

          # Not allowed
          for ( 1 .. 10 ) {
              say $_;
          } if 1;
          
          # Allowed
          map { say $_ } ( 1 .. 10 ) if 1;
      
      The idea seems to be that a post-conditional should be simple and obvious by the time you've parsed the statement. A block makes it confusing, as does mixing post control structures

      Post-loop structures have the same limitation:

          # Not allowed
          { say "foo"; say $_ } for ( 1 .. 10 );
          
          # Allowed
          say $_ for ( 1 .. 10 );
      
      When used correctly, these can become very succinct and clear.

          my %data = get_data_record_hash("foo");
          
          $data{$_} = update_field(data{$_}) for ('field1','field4',other_field');
          
          # Compare to map, which normally you expect to return values
          map { $data{$_} = update_field(data{$_}) } ('field1','field4',other_field');
      
      The single statement limitation keeps you from going wild in ways that are probably not useful to future readers of the code (including you).
    • Gaelan 9 years ago

      You can write gibberish in any language. You shouldn't kill useful features because they are sometimes misused.

      • olalonde 9 years ago

        > You shouldn't kill useful features because they are sometimes misused.

        That is highly debatable.

      • ghurtado 9 years ago

        Not even PHP's "register_globals"?

        • Gaelan 9 years ago

          I'd argue that register_globals is never a good idea. Post-if is sometimes a good idea, with one case that is problematic (less readable, not even a negative affect on function)

  • yellowapple 9 years ago

    To clarify a bit: postfix conditionals actually originate from Perl, and are one of several things that Ruby inherited as a Perl descendant.

    In Perl:

        if ($x > 0) { $y += $x }
    
    versus:

        $y += $x if $x > 0;
codr4life 9 years ago

Not worth the effort; modern compilers are very helpful with identifying these kinds of slip ups; and they weren't really that common to begin with, not common enough to warrant the attention and energy sucked into this never ending argument. It's a rule for the sake of having rules, like so many others.

MilnerRoute 9 years ago

I didn't know abut "Yoda conditions," and thought it was going to be those "reversed" if statements that were so startling when I'd started coding in Perl.

      $days = 28  if $month eq "February";
rocky1138 9 years ago

I don't follow this convention, but something I do follow is naming things by type first. This makes it really easy to find in a long list of files in a folder.

Example: plantArrowhead, plantGuzmania, plantMarmo vs. arrowheadPlant, guzmaniaPlant, marmoPlant.

alexeiz 9 years ago

Yoda conditions suck. You don't compare a constant to a value of a variable to make sure that the constant has a certain value. Such comparison is backwards. Unfortunately it is extremely popular at Microsoft among C++ programmers. They seem to totally lack any sense of style. Sometime in the nineties the enforcement of Hungarian notation throughout the company had a long lasting damaging effect on their psyche they still can't recover from after all these years.

pier25 9 years ago

ESLint has a rule to prevent this.

http://eslint.org/docs/rules/yoda

oli5679 9 years ago

Interesting idea this is.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection