Ruby | 再定義できる演算子 で組み込み演算子風のメソッドを定義する #ruby

# 再定義できる演算子 で組み込み演算子風のメソッドを定義する
## :musical_score: 概要
再定義できる演算子 で組み込み演算子風のメソッドを定義する

## :musical_note: Ruby の演算子はメソッドのシンタックスシュガー
多くの言語では論理演算子や算術演算子などは、言語の組み込み機能であり、
通常の関数やメソッドとは別の扱いであると思います。

Ruby の場合、これらの演算子の多くが、ただのメソッドとして定義されています。

### <i class="fa fa-flask" style="font-size:1em;"></i> `+` 演算子を利用した文字列の結合の例
例えば Ruby で文字列を結合する際に利用される + 演算子は実際の所
<i class="fa fa-external-link" style="font-size:1em;"></i> [String#+](http://docs.ruby-lang.org/ja/2.2.0/method/String/i/=2b.html) という String のメソッドに過ぎません。

~~~ruby
print "hoge" + "hage" # => hogehage

# 上記は以下のシンタックスシュガー
print "hoge".+("hage") # => hogehage
~~~

下記ページの再定義可能な演算子（メソッド）の記号については、ドットを省略して呼び出すことで演算子の見た目にすることができます
<i class="fa fa-external-link" style="font-size:1em;"></i> [るりま | 演算子式](http://docs.ruby-lang.org/ja/2.2.0/doc/spec=2foperator.html)

* 再定義できる演算子(メソッド)

~~~
|  ^  &  <=>  ==  ===  =~  >   >=  <   <=   <<  >>
    +  -  *  /    %   **   ~   +@  -@  []  []=  ` ! != !~
~~~

### <i class="fa fa-flask" style="font-size:1em;"></i> `+` 演算子を利用した配列の結合の例
例えば Ruby で配列を結合する際に利用される + 演算子は実際の所
<i class="fa fa-external-link" style="font-size:1em;"></i> [Array#+](http://docs.ruby-lang.org/ja/2.2.0/method/Array/i/=2b.html) という Array のメソッドに過ぎません。

~~~ruby
print [1, 2, 3] + [4, 5, 6] # => [1, 2, 3, 4, 5, 6]

# 上記は以下のシンタックスシュガー
print [1, 2, 3].+([4, 5, 6]) # => [1, 2, 3, 4, 5, 6]
~~~

## :recycle:<i class="fa fa-edit" style="font-size:1em;"></i> 演算子のオーバーライド
メソッドに過ぎないということは、オーバーライドも可能です。
Ruby の特徴的な機能であるオープンクラスを利用して、 [^1]
String#+ をオーバーライドしてみます。

数字のみで構成される文字列同士の + の呼び出し時のみ
数値演算を行うようにしてみます。

~~~ruby
puts "hoge" + "hage" # => hogehage
puts "1" + "2" # => 12

class String
  NUMBER_REGEXP = /^\d+$/

  def +(other)
    # 数値同士の場合のみ数値演算として加算
    if number?(self) && number?(other)
      self.to_i + other.to_i
    # 数値同士の場合以外は文字列演算として結合
    else
      "#{self}#{other}" 
    end
  end

  private

  def number?(value)
    value =~ NUMBER_REGEXP
  end
end

puts "hoge" + "hage" # => hogehage
puts "1" + "2" # => 3
~~~

## :musical_note: 自作クラスに演算子風のメソッドを追加してみる
Team クラスを作成して  

* `+`  : メンバーの追加
* `-`  : メンバーの削除
* `<<` : チームまるごとメンバーに追加

というクラスを作成してみます。

### <i class="fa fa-code" style="font-size:1em;"></i> ソースコード
~~~
class Team
  attr_reader :members

  def initialize(members = [])
    @members = members
  end

  # メンバーの追加
  def +(member)
    @members << member
  end

  # メンバーの削除
  def -(member)
    @members.delete(member)
  end

  # チームの追加
  def <<(other)
    fail 'error' unless other.respond_to?(:members)
    @members += other.members
  end
end

team_a = Team.new(%w(tanaka sato suzuki))
print team_a.members, "\n"
team_a + 'obokata'
print team_a.members, "\n"
team_a - 'obokata'
print team_a.members, "\n"

team_b = Team.new(%w(matz larry guido))
team_a << team_b
print team_a.members, "\n"

begin
  team_a << "not Team class"
  print team_a.members, "\n"
rescue => e
  puts e.message
end
~~~

### :running: 実行結果

~~~
["tanaka", "sato", "suzuki"]
["tanaka", "sato", "suzuki", "obokata"]
["tanaka", "sato", "suzuki"]
["tanaka", "sato", "suzuki", "matz", "larry", "guido"]
error
~~~

## :computer: 演算子風メソッドのユースケース
* 演算子風のメソッドを定義した方が、コードとして自然にみえる、理解しやすい場合
* 内部 DSL で演算子風のメソッドを提供したほうが設定として自然にみえる場合

## :books: 外部資料
* <i class="fa fa-external-link-square" style="font-size:1em;"></i> [るりま 演算子式](http://docs.ruby-lang.org/ja/2.2.0/doc/spec=2foperator.html)

## :books: 脚注
[^1]: Rubyでは（組み込みクラスを含め）定義済みクラスにメソッドを追加・変更することができます。