Range オブジェクトの条件
Range オブジェクトを生成するクラスには、スペースシップ演算子「<=>」と succ メソッドが必要。
class Range (Ruby 2.4.0)
https://docs.ruby-lang.org/ja/latest/class/Range.html
範囲演算子のオペランドは互いに <=> で比較できる必要があります。 さらに Range#each を実行するためには succ メソッ ドを実行できるものでなければいけません。
スペースシップ演算子「<=>」は順序を比較するための演算子。
module Comparable (Ruby 2.4.0) https://docs.ruby-lang.org/ja/latest/class/Comparable.html
self <=> other は
・self が other より大きいなら正の整数
・self と other が等しいなら 0
・self が other より小さいなら負の整数
・self と other が比較できない場合は nilをそれぞれ返すことが期待されています。
succ メソッドは、「次のオブジェクト」を返すメソッド。
「次のオブジェクト」はクラスによってそれぞれ定義されている。
instance method Integer#next (Ruby 2.4.0)
https://docs.ruby-lang.org/ja/latest/method/Integer/i/next.html
self の次の整数を返します。
instance method String#next (Ruby 2.4.0) https://docs.ruby-lang.org/ja/latest/method/String/i/next.html
self の「次の」文字列を返します。
「次の」文字列は、対象の文字列の右端から アルファベットなら アルファベット順(aの次はb, zの次はa, 大文字も同様)に、 数字なら 10 進数(9 の次は 0)とみなして計算されます。
instance method Date#next (Ruby 2.4.0)
https://docs.ruby-lang.org/ja/latest/method/Date/i/next.html
翌日の日付オブジェクトを返します。
Range オブジェクトを生成できるクラスを実装する
スペースシップ演算子「<=>」と succ メソッドを実装したシンプルなクラスのコードを書いてみた。
class RangeImpl
attr_reader :value
def initialize(value)
puts "initialize: #{value}"
@value = value
end
def <=>(other)
puts "<=>: #{value}"
@value <=> other.value
end
def succ()
puts "succ: #{value}"
RangeImpl.new(@value + 1)
end
end
puts '[create range]'
r = RangeImpl.new(1)..RangeImpl.new(5)
puts '[to array]'
a = r.to_a
実行結果。
スペースシップ演算子「<=>」と succ メソッドがどのタイミングでコールされているかわかる。
$ ruby range.rb
[create range]
initialize: 1
initialize: 5
<=>: 1
[to array]
<=>: 1
succ: 1
initialize: 2
<=>: 2
succ: 2
initialize: 3
<=>: 3
succ: 3
initialize: 4
<=>: 4
succ: 4
initialize: 5
<=>: 5
範囲演算子「..」の際に、最初と最後のオブジェクトが生成される。
その後、実際に使うタイミング (今回は to_a メソッドで配列化するとき) で succ メソッドを利用して、範囲内のオブジェクトが順番に生成されている。
ちなみに、スペースシップ演算子「<=>」が実装されていない場合は「bad value for range (ArgumentError)」という例外が発生し、 succ メソッドが実装されていない場合は「in `each': can't iterate from RangeImpl (TypeError)」という例外が発生した(Ruby 2.4.1の場合)。
参考資料
- class Range (Ruby 2.4.0) https://docs.ruby-lang.org/ja/latest/class/Range.html
- Using ruby Range with custom classes - Arkency Blog http://blog.arkency.com/2014/08/using-ruby-range-with-custom-classes/