Ruby ternary operator

2019-08-27 20:19发布

问题:

Why aren't these two statements equivalent?

  1. defined? foo ? foo << "bar" : foo = ["bar"]
  2. if (defined? foo) then foo << "bar" else foo = ["bar"] end

First statement:

irb(main):001:0> defined? foo ? foo << "bar" : foo = ["bar"]
=> nil
irb(main):002:0> foo
=> nil
irb(main):003:0> defined? foo ? foo << "bar" : foo = ["bar"]
=> "expression"
irb(main):004:0> foo
=> ["bar"]

Second statement:

irb(main):001:0> if (defined? foo) then foo << "bar" else foo = ["bar"] end
=> ["bar"]
irb(main):002:0> foo
=> ["bar"]
irb(main):003:0> if (defined? foo) then foo << "bar" else foo = ["bar"] end
=> ["bar", "bar"]
irb(main):004:0> foo
=> ["bar", "bar"]

These sessions are with JRuby 1.5.0 (should be equivalent to native Ruby 1.8.7). I see slightly different behavior with native ruby 1.9.1: statement #1 never defines foo even when running it twice.

回答1:

Because the first evaluates to:

defined?(foo ? foo << "bar" : foo = ["bar"])

Why that returns nil, I have no clue...

The fix is simply to do:

(defined? foo) ? foo << "bar" : foo = ["bar"]


回答2:

  1. defined? foo ? foo << "bar" : foo = ["bar"]
  2. if (defined? foo) then foo << "bar" else foo = ["bar"] end

In either case, your code could probably be simplified. Based on the two samples above it looks like there'd be an enclosing loop of some sort. Rather than try to create foo inside it and initialize to the first ['bar'], I'd do something like:

foo = []
... start some loop ...
  foo << bar
... end some loop ...

Or, if you don't like breaking up the initializer from where you append to the array:

(foo ||= []) << "bar"

This second way is a bit "Perlish", but it's not so far gone from the Ruby-way to be undecipherable.