+ (足す)から始めるRuby

数学の世界で + は演算子である
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 # => "*******12345********"

3333456456 / 3 # => 4
3333456456 / 5 # => 2

12345 ** 7 # => "*12345**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ではうまくいきません