オブジェクトの順序

自作クラスのオブジェクトを Array に詰めて sort してみる。

class OreOre
  def initialize(val) @value = val end
  attr_accessor :value
  def inspect; "o:#{@value}" end
end

ary = 5.downto(1).inject([]){|a, i| a << OreOre.new(i)}
            # => [o:5, o:4, o:3, o:2, o:1]

ary.sort    # ~> -:8:in `sort': undefined method `<=>' for 5:OreOre (NoMethodError)

オブジェクトの大小関係を調べるためには UFO 演算子を使うらしい。

class OreOre
  def <=>(o)
    @value - o.value    # テキトーだなぁ
  rescue
    nil
  end
end

ary.sort    # => [o:1, o:2, o:3, o:4, o:5]

できた。

UFO 演算子は左辺と右辺を比較して結果を数値で返す。

左辺 <=> 右辺の値は、以下の値を返す約束になっている。

左辺 <=> 右辺
左辺 < 右辺 -1 (負数)
左辺 == 右辺 0
左辺 > 右辺 1 (正数)
比較不能 nil

ところが、このままだとふつうの比較ができない。

o0 = OreOre.new(0)
o1 = OreOre.new(1)
o0 < o1     # ~> -:21:in `<top (required)>': undefined method `<' for 0:OreOre (NoMethodError)
o0 > o1     # ~> -:22:in `<top (required)>': undefined method `>' for 0:OreOre (NoMethodError)

<, <=, >=, > をそれぞれ定義しても良いが、<=> があれば Comparable が自動生成してくれる。

class OreOre
  include Comparable
end

o0 = OreOre.new(0)
o1 = OreOre.new(1)
o0 < o1     # => true
o0 > o1     # => false

ここまでくると、次に気になるのは Range の端点になれるか?

o2 = OreOre.new(2)
o3 = OreOre.new(3)
r = o0..o2      # => o:0..o:2
r.cover?(o1)    # => true
r.cover?(o3)    # => false

できてるかと思いきや、

r.include?(o1)
# ~> -:34:in `each': can't iterate from OreOre (TypeError)
r === o1
# ~> -:35:in `each': can't iterate from OreOre (TypeError)
r.each{|o| puts o.value}
# ~> -:36:in `each': can't iterate from OreOre (TypeError)

やっぱりダメ。

たぶん each するには succ がいるんだよね。

class OreOre
  def succ
    OreOre.new(@value+1)    # テキトーすぐる
  end
end

r.include?(o1)    # => true
r === o1          # => true
r === o3          # => false

r.each{|o| puts o.value}
# >> 0
# >> 1
# >> 2

できた。

けど、ふつうは、こんな簡単に succ 実装できないよな。