数学の世界で + は演算子である。5歳の子供でもそれを知っている。そして私やあなたが老いて死にゆくまで、+ は演算子でありそこに疑念の入る余地はない。

プログラミングの世界でもふつう + は演算子である。CでもJavaでもPerlでも + は演算子であり、それ以上でもそれ以下でもない。

ところが驚くべきことに、Rubyの世界では + は演算子ではないのである。

嘘だと思うなら、エディタを立ち上げて1、次のようにしてみるといい。

class Fixnum
  def +(other)
    Integer("#{self}"+"#{other}")
  end
end
1 + 2 # => 12
123 + 456 # => 123456

あなたは今、Fixnum#+ メソッドを再定義した。そうしたら1 + 2は12という答えを返した。そう、Rubyの世界で + は演算子ではなく、ユーザが再定義可能なひとつのメソッドに過ぎないのだ。

つまり 1 + 2 や 123 + 456 の構文は、以下のシンタックスシュガーである。

1.+(2) # => 12
123.+(456) # => 123456

疑い深いあなたはこれだけでは納得しないかも知れない。そしてきっと、他の演算子についても試してみるのだろう。

class Fixnum
  def +(other)
    Integer("#{self}"+"#{other}")
  end
  alias :minus :-
  def -(other)
    res = self.minus(other)
    res > 0 ? res : "unknown world for me."
  end
  def *(other)
    "#{self}".center(other, "*")
  end
  def /(other)
    "#{self}".count("#{other}")
  end
  def **(other)
    self * other * other
  end
end
1 + 2 # => 12
123 + 456 # => 123456
9 - 4 # => 5
21 - 34 # => "unknown world for me."
12345 * 20 # => "******##*******"
3333456456 / 3 # => 4
3333456456 / 5 # => 2
12345 ** 7 # => "##*12345**12345**12345**12345**12345**12345*"

納得した?

そうRubyの世界では + だけでなく、演算子のほとんどがメソッド呼び出しなのである。

演算子をメソッドにする利点は2つある。1つは今見たようにそれが再定義可能であることだ。しかしより大きな利点は2つ目にある。

上で再定義したFixnum#+の中身を注意深く見てほしい。 + の定義の中で + が使われているのが分かるだろう。通常このような定義は、定義が定義を呼び出すことになってうまく働かない。しかしここでは問題なく動いている。

もうあなたは気付いているに違いない。そう、Fixnum#+の定義の中の + はFixnumの+を呼び出しているのではなく、Stringの+を呼び出しているのだ。そしてString#+は文字列を結合するべく定義されている。再定義されたFixnum#**の結果が理解できるなら、あなたはこれを正しく理解している。

これで演算子をメソッドにする2つ目の利点が分かっただろう。つまりRubyでは、異なる種類のオブジェクトごとに同じ演算子を持てるのだ。そして各オブジェクトの演算子はその属するクラスにおいてそれぞれ適切に定義される。

Rubyにおいて + 演算子は数と文字列だけでなく、配列と時間のオブジェクトでも標準で使える。

[1,2,3] + [4,5,6] # => [1, 2, 3, 4, 5, 6]
now = Time.now # => 2011-08-10 17:36:10 +0900
now + 60*60 # => 2011-08-10 18:36:10 +0900

もちろん標準クラスのメソッドを改変することのリスクをあなたは理解するだろう。それはすべてに影響する。しかしその影響を最小限に抑えて、機能を拡張するような改変は許容できるだろう。

文字に整数を足したり引いたりして、文字コードにおける並びの文字を返すよう拡張した例を示そう。

class String
  alias __plus__ +
  def +(other)
    if other.is_a? Integer
      return (self.ord + other).chr
    end
    __plus__(other)
  end
  def -(other)
    case other
    when String
      self.ord - other.ord
    when Integer
      (self.ord - other).chr
    else
      raise ArgumentError
    end
  end
end
'a' + 'b' # => "ab"
'a' + 5 # => "f"
'f' - 'a' # => 5
'f' - 5 # => "a"

String#+はその引数に文字列が渡されたときは、標準の動作に従って文字列を結合する。数字が渡されたときには、文字コードにおいてその分シフトした文字列を返す。

もちろんあなたのオブジェクトにも + 演算子を自由に定義できる。早速やってみよう。

class City
  attr_reader :name
  def initialize(name)
    @name = name
  end
  def +(other)
    City.new(name+other.name)
  end
end
c1 = City.new('buda') # => #<City:0x00000100869e20 @name="buda">
c2 = City.new('pest') # => #<City:0x000001008699c0 @name="pest">
c3 = c1 + c2 # => #<City:0x00000100869600 @name="budapest">
c3.name # => "budapest"

2つのCityオブジェクトを合わせると新しい都市が生まれた!

このようにしてRubyでは、通常の算術演算子をその対象のオブジェクトに応じて、それに則した用途として定義できる。これはプログラミングを極めて直感的なものにするのである。

姉妹シリーズ「1から始めるRuby」もよろしくね:)

  1. irbではうまくいきません


blog comments powered by Disqus
ruby_pack8

100円〜で好評発売中!
M'ELBORNE BOOKS