Ruby的&&运算符

4
在PHP中,这将被评估为true:
$a = 1
$b = 2

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

在 Ruby 中,这个表达式的值为 2
a = 1
b = 2
p a && b # 2

为什么Ruby会返回最后一个语句的值(当第一个和第二个语句都为真时),而不是返回布尔值?

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

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)
2个回答

6
这里涉及到几个方面。
为什么 Ruby 返回最后一个语句的值(当第一个和第二个语句都为 true 时),而不是返回布尔值?
因为这样更有用。事实上,这种语义非常有用,以至于 PHP 7 添加了它(但作为新操作符“??”)。在 Ruby 中,就像在 PHP 中一样,所有值都是真值或假值。与 PHP 不同的是,Ruby 对此有更严格的理解:只有 false 和 nil 是假值,其他所有值都是真值。这使您可以轻松设置默认值。
name = options[:name] || "John Doe"

如果options[:name]未被找到并返回nil,那么该部分为假,||右侧的内容将被返回;否则,将返回options[:name]
在大多数情况下,您不需要布尔值,因为真或假已经足够了。如果您确实非常想要一个布尔值,例如为了防止从类中泄漏私有信息,则习语!!value很常见:
def has_name?
  !!(self.name || self.nickname)
end
!(否定)的结果始终为布尔值;如果您两次否定,会将真值转换为true,将虚值转换为false
最后,

让我困扰的是while中的条件 - while a_next = a.next && b_next = b.next - 这样写,它总是设置a_next = b_next(第一个问题似乎跟这种行为有关)。但是当我将两个&&操作数括起来时 - 它按预期工作 - (a_next = a.next) && (b_next = b.next) # works ok

然后您需要将它们包装起来。这是由于运算符优先级的原因,并且按照设计工作,因为更正常的写法是
blue_green = colour == :blue || colour == :green

than

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 运算符而不是 &&|| 是一个常见错误,以至于许多样式指南直接禁止它们(它们仅在此上下文中有用 - 在循环条件内部分配 - 并且可以用括号解决)。例如:

andor 关键字被禁止。这根本不值得。始终使用 &&||


非常感谢您的详细解释!它确实帮助我在头脑中澄清了一些事情。 :) - brslv

6
考虑到真值和伪值与true和false的区别
考虑以下内容:
    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(假),而其他所有值都被视为逻辑上的true(真)。因此,通常不需要将假值转换为false或将真值转换为true。例如,我们可以编写如下代码:
3 ? "happy" : "sad"
  #=> "happy"
nil ? "happy" : "sad"
  #=> "sad"

将真值和假值转换为 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]

另一个例子是对整数或 nil 值的数组arr中的元素进行求和:

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的所有元素后,执行a.next将会引发StopIteration异常 (参见Enumerator#next)。我之所以选择Kernel#loop而不是while循环,是因为前者通过跳出循环来处理StopIteration异常。因此,不需要使用rescue进行捕获(顺便说一下,如果您确实想在while循环中使用rescue,则应该使用rescue StopIteration而不是捕获所有异常)。

非常好的解释,Cary!谢谢你提供干净、有意识的例子。它们帮助我更好地理解了不仅仅返回 true/false 的可能性。我已经基本习惯了这种方式,我很喜欢!再次感谢 :) - brslv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接