在 Ruby (1.9.3) 中,为什么 nil 响应比较运算符,`<=>`?

In Ruby (1.9.3), why does nil respond to the comparison operator, `<=>`?

对我来说,将 null 类型与其他任何东西(甚至是另一个 null 类型)进行比较是未定义的操作。如有不妥请指正

根据该假设,以下内容对我来说有意义:

nil.is_a? Comparable
 => false

nil.respond_to? :<=
 => false

nil.respond_to? :<
 => false

nil.respond_to? :>=
 => false

nil.respond_to? :>
 => false

但是,nil确实响应"spaceship"比较运算符:

nil.respond_to? :<=>
 => true

我想不出比较 nil 有什么意义,更不用说实用了。为什么 nil 有这种行为?

nil in Ruby 是 NilClass 的单例实例,它继承自 Object。对象实现 <=>,其行为定义为:

Returns 0 if obj and other are the same object or obj == other, otherwise nil. 0 means self is equal to other. 1 means self is bigger than other. Nil means the two values could not be compared.

(见the documentation

因此,nil <=> nilreturns 0(它们是等价的),但是nil <=> anything_elsereturns nil,这意味着"could not be compared" .

在Ruby中,期望所有对象都响应<=>(包括nil),但对于无意义或未定义操作的对象,return 的值为 nil,然后可以在调用代码认为最合适的情况下对其进行处理。在Enumerable的操作如#sort的情况下,它会引发异常:

[1, nil].sort
# => ArgumentError: comparison of NilClass with 1 failed

但不一定;您可以实现自己的排序,将无法排序的值移动到列表的开头:

[1, nil, 2, 3, nil].sort {|a, b| (a <=> b) || -1 }
# => [nil, nil, 1, 2, 3]

Object#<=>nil 有多大用处?我想这只是受限于一个人的想象力。

示例 #1

这里有一个简单的例子来说明它的用处。假设您希望对数组进行排序:

arr = [1,nil,3,nil,2]

所有 nil 排在第一位,所以它 return:

[nil, nil, 1, 2, 3]

为:

nil<=>nil #=> 0

并且,对于所有非nil对象a

nil<=>x   #=> nil
x<=>nil   #=> nil

我们可以这样写:

arr.sort { |a,b| (a<=>b) ? a<=>b : a.nil? ? -1 : 1 }
  #=> [nil, nil, 1, 2, 3]

示例 #2

现在让我们考虑第二个更有趣的例子。假设我们的剧院演出票超卖了,必须拒绝一些顾客,并给他们退款。散列 tickets 显示每个人为他们的票支付的费用:

ticket_holders = { 'bob'=>10, 'lucy'=>15, 'cher'=>5, 'arnold'=>12 }

我们希望尽量减少退款,但不希望因拒绝名人而造成负面宣传,原因如下:

celebrities = ['arnold', 'cher']

所以我们会给他们最高的优先级。因此,我们希望按降序对 ticket_holders 进行排序,除非我们希望其键位于 celebrities 中的键值对排在第一位。也就是说,我们希望结果为:

['cher', 'arnold', 'lucy', 'bob']

['arnold', 'cher', 'lucy', 'bob']

让我们寻找一个通用的解决方案:

module Enumerable
  def sort_by_nils_first
    sort do |a,b|
      av = yield(a)
      bv = yield(b)
      (av<=>bv) ? av<=>bv : av.nil? ? -1 : 1
    end
  end
end

我们这样应用:

ticket_holders.sort_by_nils_first { |name,price|
  celebrities.include?(name) ? nil : -price }.map(&:first)
  #=> ["arnold", "cher", "lucy", "bob"] 

单看世界上名人的数量,以及对待他们的方式,我认为这是一个非常有用的方法。

应用前面的例子,我们得到:

[1,nil,3,nil,2].sort_by_nils_first(&:itself)
  #=> [nil, nil, 1, 2, 3]

我在 v2.2.

中使用了 Object#itself

sort_by_nils_first 当然可以在没有给出块时修改为 return 和 Enumerator,使其与 Enumerable#sort_by.

相当