Ruby's && operator

时间:2016-12-02 04:53:49

标签: ruby iterator conditional

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).

2 个答案:

答案 0 :(得分:5)

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.

答案 1 :(得分:5)

Truthy and Falsy与truefalse

考虑

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

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

回想一下,在Ruby中,falsenil按逻辑方式评估false(“falsy”),其他所有内容都按逻辑true(“truthy”)进行评估。因此,通常无需将假值转换为falsetruthy值为true。我们可以编写,例如,

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

将Truthy和Falsy转换为truefalse

如果您坚持,可以使用双惊叹技巧将truthyfalsey值转换为truefalse

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

&&|| 的交易技巧

&&||定义它们的方式通常非常方便。例如,如果h是哈希,假设我们写

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

如果h没有密钥kh[k] #=> nil,那么表达式会缩小为

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

另一个例子是对数组arr的元素求和,它们是整数或nil

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

还有许多其他创造性的方法可以在Ruby中使用这两个操作符,如果他们刚刚返回truefalse则无法实现。

代码段

现在让我们转到您提到的代码段。

首先,你的救援条款有点分散注意力,while的论点总是正确的,因此有点人为,所以让我们写下你的内容如下。

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] 

a.next的所有元素都被枚举后执行a时,它会引发StopIteration异常(请参阅Enumerator#next)。我选择Kernel#loop而不是while的原因是前者通过突破循环来处理StopIteration例外。因此,不需要救援。 (顺便说一句,如果你使用while进行救援,你需要rescue StopIteration,而不是拯救所有例外。)