为什么在Ruby中sort或太空船(飞碟)运算符(<=>)不能用于布尔值?

16
在"Is it possible to sort a list of objects depending on if the individual object's response to a method?"这篇文章中,我发现飞碟在布尔值上无法正常工作。
考虑以下内容:
Ruby 1.8.7:
[true, false].sort # => undefined method `<=>' for true:TrueClass (NoMethodError)
true <=> false     # => undefined method `<=>' for true:TrueClass (NoMethodError)

Ruby 1.9.3:

Ruby 1.9.3:
[true, false].sort # => comparison of TrueClass with false failed (ArgumentError)
true <=> false     # => nil
true <=> true      # => 0
false <=> true     # => nil

这可能与true和false没有固定的排序顺序有关,因为哪个先出现呢?但是,我认为这听起来相当薄弱。

这是sort函数中的一个bug吗?


2
不要把你的误解归咎于 Ruby 的错误(bug)。 - sawa
1
或许问题应该是:为什么要实现布尔类型的 '<=>' 运算符呢? - PinnyM
2
@AlexChaffee 我明白了。这有点奇怪。<=> 应该始终返回 0-11,或者未定义。返回 nil 违反了预期。我同意。 - sawa
2
@sawa:<=> 的返回值为 -101nil,分别表示小于等于大于不可比较。这是 <=> 的标准协议,我不明白布尔值的实现如何违反该协议。true 等于 true,但它与 false 不可比较。 - Jörg W Mittag
1
可比较!啊哈!所以 sort 想说的是:“嘿,蠢货,true 和 false 是不可比较的;试着对一些实现了 Comparable 模块的对象进行排序”,但它输出的是“comparison of TrueClass with false failed”,这并没有什么帮助。 - AlexChaffee
显示剩余8条评论
4个回答

12

布尔值没有自然顺序。

Ruby 语言的设计者可能认为为布尔值发明排序会让开发者感到惊讶,因此故意省略了比较运算符。


是的。除了<>之外,<=>确实被实现了。在Ruby 1.9中,<=>被添加到了Object类中(参见http://ruby-doc.org/core-1.9.3/Object.html#method-i-3C-3D-3E),它的实现让sort函数对true和false感到困惑。 - AlexChaffee
1
@AlexChaffee:啊,我明白了,这意味着布尔值的太空船运算符有问题。真烂! - maerics
1
@maerics:对于布尔值,<=> 的实现完全符合 <=> 协议。它在哪方面出了问题? - Jörg W Mittag
1
@JörgWMittag:嗯,我以为<=>运算符是用于类似排序的比较(返回值-1、0、1),但在阅读更多文档(例如Numeric)后,似乎并非如此。所谓“破坏”,是因为它违反了我认为太空船运算符实现比较的约定,让我感到困惑。 - maerics
顺便提一下,我接受了这个答案,但应该注意到完整的故事实际上在问题本身的评论中。 - AlexChaffee
2
那只是一个错误的决定。布尔代数基本上是一个格子,而顶部和底部(真和假)肯定是可比较的。 - user824425

9
所谓的飞碟需要所有比较运算符(<>==)工作(从技术上讲不需要,尽管理论上是这样)。truefalse之间互不小于或大于。对于nil也是如此。为了实现一个实用的解决方法,您可以将其“转换”为整数(false为0,true为1)。例如:
[true, false, true].sort_by{|e| e ? 1 : 0}

我知道“真和假不是彼此之间的小于或大于” - 我想知道为什么Ruby被设计成这样?使用简单的[true,false] .sort@products.sort_by(&:on_sale?)对布尔数组进行排序似乎更有用,而不是在通常如此弹性的语言中出现突然的失败情况。 - AlexChaffee
2
作为一个设计问题,这可能是因为人们无法真正猜测开发者的意图。当比较 true > false 时,您期望看到什么值?在某些语言中,false 被等同于 0,而 true 可能是 1 或 -1。这是一个任意的决定,设计者可能觉得应该由开发者来决定。顺便说一句,PHP 尝试解决了这个问题,但可能在这个过程中让它更加混乱了... - PinnyM
1
飞碟不需要所有比较运算符才能工作。相反,只需定义 <=> 并包含 Comparable 模块,所有比较方法都可以“免费”定义。 - steenslag
@steenslag:从技术角度来说是正确的,但是核心思想保持不变。 <=> 的实现应该代表比较运算符的功能。已进行更新以澄清此问题-感谢指出。 - PinnyM
@AlexChaffee:你为什么期望Ruby能够对无法排序的东西进行排序呢? - Jörg W Mittag
顺便说一句,即使您有一个好的解决方案,我也不接受这个答案,因为它包含了一个错误。"<=>" 不需要 "<", ">", 等等,而 Comparable 实现 了 "<", ">", 等等,就是通过 "<=>" 来实现的。 - AlexChaffee

2

布尔值没有天然的顺序。与C语言不同,false并不小于true,它们只是等效和同样有效的状态。但是可以使用一个块来配置任何排序方式,例如:

ary = [true, false, false, true]
ary.sort {|a,b|  a == b ? 0 : a ? 1 : -1 }

# => [false, false, true, true]

反转顺序也很简单:

ary.sort {|a,b|  a == b ? 0 : a ? -1 : 1 }

# => [true, true, false, false]

0

我知道这已经很老了,但最近它却折磨着我。但这是 Ruby,对吧?那么怎么样进行这种“猴子补丁”呢?

#! /usr/bin/env ruby

class TrueClass
  include Comparable

  def <=>(other)
    if other.class == FalseClass
      1
    elsif other.class == TrueClass
      0
    else
      nil
    end
  end
end

class FalseClass
  include Comparable

  def <=>(other)
    if other.class == TrueClass
      -1
    elsif other.class == FalseClass
      0
    else
      nil
    end
  end
end

puts "true <=> false: #{true <=> false}"
puts "false <=> true: #{false <=> true}"
puts "true <=> true: #{true <=> true}"
puts "false <=> false: #{false <=> false}"
puts "true <=> 13: #{true <=> 13}"
puts "true > false: #{true > false}"
puts "true < false: #{true < false}"
puts "true == false: #{true == false}"
puts "true == true: #{true == true}"
puts "false == false: #{false == false}"
puts "false < true: #{false < true}"
puts "[false, true, false].max: #{[false, true, false].max}"
puts "[false, true, false].min: #{[false, true, false].min}"

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