範囲(Range)

実装されている抽象概念が豊富なこともRubyの魅力だ。

例えば「範囲」。我々がいろいろな所で半ば無意識に使う概念だが、Rubyでは範囲演算子を使ってこれを直接表現できる。

1..12

これは見た目通り1〜12の整数の範囲を表しているのだが(".."は両端終端を含み、"..."は両端終端を含まない範囲とすることができる)、このように記述した範囲は内部ではRangeクラスの定数に変換されていることになる。
Rangeクラスはそれ自体、メソッドを持っており範囲に対して行いたい操作メッセージを送信することができる。

irb(main):001:0> a = 1..12
=> 1..12
irb(main):002:0> a.include?(5)
=> true
irb(main):003:0> a.min
=> 1
irb(main):004:0> a.max
=> 12
irb(main):005:0> a.to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

さて、ここからが真骨頂。
Rubyは独自の範囲を作ることが出来る上に、範囲を作るためにはRangeクラスを継承したクラスにする必要が無い。
単に範囲を作るだけであれば、Comparableモジュールをmixinして演算子 "<=>" と succメソッド※に対する応答を用意すれば良い。

bmi.rb

class BMI 
  include Comparable
  attr :volume
  def initialize(volume)
    @volume = volume
  end
  def inspect
    return '痩せ' if 18.5 > @volume
    return '標準' if (18.5..24) === @volume
    return '肥満' if (25..29) === @volume
    return '高度肥満' if 30 <= @volume
  end
  def <=>(other)
    self.volume <=> other.volume
  end
  def succ
    BMI.new(@volume.succ)
  end
end

mybmi = BMI.new(18.5)
p mybmi

mybmi = BMI.new(15) .. BMI.new(27)
p mybmi
p mybmi.to_a

実行結果

>ruby -Ks bmi.rb
標準
痩せ..肥満
[痩せ, 痩せ, 痩せ, 痩せ, 標準, 標準, 標準, 標準, 標準, 標準, 肥満, 肥満, 肥満]

実装するのにインタフェースすら書かなくても良いのはちょっと感動。

範囲はいろいろな概念をコードに変換するのに重宝するだろう。

※succって、successorの省略かな。