Ruby's && operator

天涯浪子 提交于 2021-02-07 17:47:30

问题


In PHP, this evaluates to true:

$a = 1
$b = 2

var_dump($a && $b); // true

In ruby, this evaluates to 2:

a = 1
b = 2
p a && b # 2

Why does ruby return the value of the last statement (when the first is true and the second one is also true) and does not return a boolean?

I have two arrays and I iterate them with an external iterator:

a = [1,2,3].to_enum
b = [5,6,7].to_enum
c = []

begin
  while a_next = a.next && b_next = b.next
    result = a_next + b_next
    p "a[x] + b[x] = c[x] = #{a_next} + #{b_next} = #{result}"
    c << result
  end
rescue 
  p c
end

The condition: while a_next = a.next && b_next = b.next sets a_next = b_next (the first question seems related to this behavior, I think). But when I wrap the two && operands, it works as expected: (a_next = a.next) && (b_next = b.next).


回答1:


There's several aspects here.

Why does ruby returns the value of the last statement (when the first is true and the second one is also true) and does not return a boolean?

Because it is more useful this way. This semantics is in fact so useful that PHP 7 added it (but as a new operator, ??). In Ruby, just as in PHP, all values are truthy or falsy. Unlike PHP, Ruby has a much stricter idea about it: only false and nil are falsy, everything else is truthy. This allows you to easily give defaults:

name = options[:name] || "John Doe"

If options[:name] is not found and returns nil, then that part is falsy and the right side of || will be returned; otherwise, options[:name] will be returned.

In most cases you don't need a boolean, because truthiness or falsiness suffices. If you really really want to have a boolean, for example in order not to leak private information out of a class, the idiom !!value is common:

def has_name?
  !!(self.name || self.nickname)
end

The result of ! (negation) is always boolean; if you negate twice, you will convert truthiness to true and falsiness to false.

Finally,

The thing that bugs me is the condition in the while - while a_next = a.next && b_next = b.next - written like this, it always sets a_next = b_next (the first question seems related to this behavior, I think). But when i wrap the two && operands - it works as expected - (a_next = a.next) && (b_next = b.next) # works ok.

Then you need to wrap them. That's due to operator precedence, and works like this by design, because it is more normal to write

blue_green = colour == :blue || colour == :green

than

blue_green = (colour == :blue || colour == :green)

There is another set of boolean operators that are actually designed to work like you propose, the only difference being the precedence, so you could write this and have it work:

while a_next = a.next and b_next = b.next

It is identical to

while (a_next = a.next) && (b_next = b.next)

A warning though: using and and or operators instead of && and || improperly is a common enough mistake that many style guides outright ban them (they are useful only in this context - assignment inside loop conditions - and it can be solved with parentheses instead). E.g.:

The and and or keywords are banned. It's just not worth it. Always use && and || instead.




回答2:


Truthy and Falsy versus true and false

Consider

    3 && 4     #=> 4 
    3 && false #=> false 
  nil && 4     #=> nil 
false && nil   #=> false 

    3 || 4     #=> 3 
    3 || false #=> 3 
  nil || 4     #=> 4 
false || nil   #=> nil 

Recall that in Ruby, false and nil evaluate as logically false ("falsy"), and everything else evaluates as logically true ("truthy"). Therefore, there's generally no need to convert a falsy value to false or a truthy value to true. We could write, for example,

3 ? "happy" : "sad"
  #=> "happy"
nil ? "happy" : "sad"
  #=> "sad"

Converting Truthy and Falsy to true and false

If you insist, you can convert truthy and falsey values to true and false with the double-exclamation trick:

!!3 => !(!3) => !(false) => true
!!nil => !(!nil) => !(true) => false

Tricks of the trade with && and ||

It's often very handy to have && and || defined the way they are. For example, if h is a hash, suppose we write

h[k] = (h[k] || []) << 3

If h does not have a key k, h[k] #=> nil, so the expression reduces to

h[k] = [] << 3
  #=> [3]

Another example is summing elements of an array arr that are integers or nil:

arr.reduce(0) { |t,n| t + (n || 0) }

There are many other inventive ways you can make use of those two operators in Ruby that would not be possible if they just returned true or false.

Code snippet

Now let's turn to the code snippet you mentioned.

Firstly, your rescue clause is a bit distracting and while's argument is always true and therefore a bit artificial, so let's write what you have as follows.

a = [1,2,3].to_enum
  #=> #<Enumerator: [1, 2, 3]:each> 
b = [5,6,7].to_enum
  #=> #<Enumerator: [5, 6, 7]:each> 
c = []
loop do
  a_next = a.next
  b_next = b.next
  result = a_next + b_next
  p "a[x] + b[x] = c[x] = #{a_next} + #{b_next} = #{result}"
  c << result
end
  # "a[x] + b[x] = c[x] = 1 + 5 = 6"
  # "a[x] + b[x] = c[x] = 2 + 6 = 8"
  # "a[x] + b[x] = c[x] = 3 + 7 = 10"
p c
  #=> [1, 2, 3] 

When a.next is executed after all elements of a have been enumerated it will raise a StopIteration exception (see Enumerator#next). The reason I chose Kernel#loop over while is that the former handles StopIteration exceptions by breaking out of the loop. Hence, no rescue is required. (btw, if you do rescue with while, you'd want rescue StopIteration, rather than rescuing all exceptions.)



来源:https://stackoverflow.com/questions/40925038/rubys-operator

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!