Ruby’s other ternary operator

This may be a well-know Ruby trick, but I thought I’d share it anyway.

Ruby has the ternary operator:

a = true  ? 'a' : 'b' #=> "a"
b = false ? 'a' : 'b' #=> "b"

But it also has something else…

a = (true  && 'a') || b #=> "a"
b = (false && 'a') || b #=> "b"

All statements in Ruby return the value of the last expression evaluated, so true && 'a' returns ‘a’, and the ‘b’ is ignored (via boolean short-circuiting). false && 'a' evaluates to false, so Ruby moves on, and returns ‘b’.

 

This is a lot like the a ||= 'a' trick, which expands to a = a || 'a', giving the variable a default value, if it’s not already initialized.

If Ruby already has the ternary operator, why bother? Personally, I find this form more natural (which surprised me, after years of using the ternary operator in Java and C#). For some reason, it reads more like natural language to me.

user_name = (user.authenticated? && user.name) || 'guest'

Does anyone else see it?

 

Advertisements

18 thoughts on “Ruby’s other ternary operator

  1. This is a well known Ruby trick, but it is not exactly the same thing as the ternary operator. Let’s compare:

    a ? b : c

    and

    (a && b) || c

    The former gives b whenever a is truthy and c otherwise, always. So if a == true, b == nil, and c == ‘cee-threepio’, (a ? b : c) => nil.

    But (a && b) || c => ‘cee-threepio’ for the same values.

    I prefer this form as well, but only when I know that the value of ‘b’ is guaranteed to be truthy.

  2. You don’t need the parantheses around your third and fourth lines. It works anyway:

    irb(main):001:0> a = true ? "a" : "b"
    => "a"
    irb(main):002:0> b = false ? "a" : "b"
    => "b"
    irb(main):003:0> a = true && "a" || b
    => "a"
    irb(main):004:0> b = false && "a" || b
    => "b"

    However, if you change && to “and”, you start running into dark corners of operator precedence:

    irb(main):001:0> a = true ? "a" : "b"
    => "a"
    irb(main):002:0> b = false ? "a" : "b"
    => "b"
    irb(main):003:0> a = true and "a" || b
    => "a"
    irb(main):004:0> b = false and "a" || b
    => false

    And why do you put “var” before user_name? Ruby isn’t javascript.

  3. Reg, you’re right, I didn’t think of that. I’ll have to scan through the areas I’ve used this, and see whether that could happen…

    Simen,
    > You don’t need the parantheses around your third and fourth lines.

    Yeah, I included them to emphasize the grouping, for clarity.

    > And why do you put “var” before user_name? Ruby isn’t javascript.

    You’re right…I think that ‘var’ snuck in from the javascript I’ve been doing, and the Scala I’ve been reading about.

  4. i think that’s how python programmers tried to get the convenience of the ternary operator into their programming language before they got their chainable syntax in version 2.5. back then, their version also failed when the variable wasn’t “truthy-worthy”.

  5. actually i think this is rather ugly

    and ternay operator is ugly too but at least used often enough to know it
    and sometimes it reduces a few lines of code

  6. you cant do this if the action you want done is a binary opertor, ie only test if a test operator was given:
    (action ? params[:action] == action : true)

  7. I think the ternary operator sucks. Ok, it’s fairly easy to understand a, b and c, but when that involves more complex expressions, it’s a pain. And there are those who like to put a ternary operator inside another operator…

  8. Do you honestly think your expression is more readable than the ternary operator which is in use in many other languages? The point is to write re-usable code that can be read and understood clearly, not to make things harder to read because you want to show you are smarter.

  9. “This is a lot like the a ||= ‘a’ trick,
    which expands to a = a || ‘a’,
    giving the variable a default value,
    if it’s not already initialized.”

    Not precisely correct – using (unnecessary) brackets to make things clear:
    whereas operators like x += y expand to x = (x + y)
    the operator x ||= y expands to x || (x = y)
    and the operator x &&= y expands to x && (x = y)

    Rick De Natale has a good post on this here:
    http://talklikeaduck.denhaven2.com/2008/04/26/x-y-redux

  10. Colin, thanks for clarifying. So, when x has a value, it’s basically a no-op?

    Funny, it expands to almost the opposite this JavaScript idiom:

    function foo(x) {
        x = x || "default";
    }
    
  11. these discussions should also include performance considerations. if you’re like me and work on back end in a production environment you often have to use code that isn’t pretty or easy to maintain but runs fast.

      • I don’t know which is faster, but I doubt it’s an order-of-magnitude difference. This was an article about language expressivity, not performance, so I didn’t worry about it.

  12. I confess I’m not too proficient with Ruby, but isn’t your ternary example wrong? I think you’re missing a couple “=” and they should be written like this:

    a == true ? ‘a’ : ‘b’ #=> “a”
    b == false ? ‘a’ : ‘b’ #=> “b”

    Of course I fully expect to be told why I’m wrong and how much I still have to learn :)

  13. No, I meant to use assignment (=), not equality testing (==). The effect I was after was to set a to “a”, and b to “b”.

    Here are some parentheses to clarify:
    a = (true ? ‘a’ : ‘b’) #=> “a”
    b = (false ? ‘a’ : ‘b’) #=> “b”

    Does that help?

Comments are closed.