Ruby 的 && 运算符

Ruby's && operator

在 PHP 中,计算结果为 true:

$a = 1
$b = 2

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

在 ruby 中,计算结果为 2:

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

为什么 ruby return 最后一个语句的值(当第一个为真且第二个也为真时)而不是 return 布尔值?

我有两个数组,我用外部迭代器对它们进行迭代:

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

条件:while a_next = a.next && b_next = b.next设置a_next = b_next(我认为第一个问题似乎与此行为有关)。 但是当我包装两个 && 操作数时,它按预期工作:(a_next = a.next) && (b_next = b.next).

这里有几个方面。

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?

因为这样更有用。这种语义实际上非常有用,以至于 PHP 7 添加了它(但作为新的运算符,??)。在 Ruby 中,就像在 PHP 中一样,所有值都是真值或假值。与 PHP 不同,Ruby 对此有更严格的想法:只有 falsenil 是假的,其他都是真值。这使您可以轻松地设置默认值:

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

如果options[:name]没有找到并且returnsnil,那么那部分是假的,||的右边将被返回;否则,将返回 options[:name]

在大多数情况下,您不需要 布尔值,因为真或假就足够了。如果你真的真的想要一个布尔值,例如为了不从 class 中泄露私人信息,惯用语 !!value 很常见:

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

!(否定)的结果总是布尔值;如果你否定两次,你会将真值转换为 true,将假值转换为 false.

最后,

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.

然后你需要把它们包起来。这是由于运算符的优先级,并且设计成这样,因为写

更正常
blue_green = colour == :blue || colour == :green

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

还有一组布尔运算符实际上设计为像您建议的那样工作,唯一的区别是优先级,因此您可以编写并让它工作:

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

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

警告:不正确地使用 andor 运算符代替 &&|| 是一个常见的错误,许多风格指南完全禁止它们(他们在这种情况下 only 有用 - 循环条件内的赋值 - 它可以用括号解决)。 E.g.:

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

Truthy 和 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")。因此,通常不需要将虚假值转换为 false 或将 truthy 值转换为 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]

另一个例子是对整数数组 arrnil:

的元素求和
arr.reduce(0) { |t,n| t + (n || 0) }

还有许多其他创造性的方法可以利用 Ruby 中的这两个运算符,如果它们只是返回 truefalse.[=53=,则这些方法是不可能的]

代码片段

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

首先,你的救援条款有点让人分心,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). The reason I chose Kernel#loop 而不是 while前者通过跳出循环来处理 StopIteration 异常。因此,不需要救援。(顺便说一句,如果你用 while 救援,你会想要 rescue StopIteration,而不是救援所有例外。)