Ruby | case と === の歴史 #ruby

# Ruby | case と === の歴史 #ruby
## 概要
Ruby の case 文と `===` の歴史について
## 昔の case
昔の Ruby には **String 化症候群** というものがあったそうです。
※参考資料([ruby-list 10525](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/10525))

case の内部では `no.to_s =~ 1.to_s` のような比較をしていた。
しかし、何でも文字列で比較するのはおかしいということで変更することになったが、
文字列以外に `=~` を利用するのも気持ち悪いので新たな演算子を利用することに。
そこで `===` の登場。
現在へ・・・

## 現在の case
現在の case 文は内部比較に `===` を利用しています。
これによって、クラスに合わせた比較処理を実現し、
case をポリモーフィックに利用することができます。

## サンプル
### String#===
[るりま | String#===](http://docs.ruby-lang.org/ja/2.1.0/method/String/i/=3d=3d.html)

文字列の内容で比較します。

#### サンプルコード
~~~ruby
%w(hoge hige hage not_hoge).each do |e|
  case e
  when 'hoge' then p "match hoge : #{e}"
  when 'hige' then p "match hige : #{e}"
  when 'hage' then p "match hage : #{e}"
  else p "no match : #{e}"
  end
end
~~~

#### 出力
~~~
"match hoge : hoge"
"match hige : hige"
"match hage : hage"
"no match : not_hoge"
~~~

### Range#===
[るりま | Range#===](http://docs.ruby-lang.org/ja/2.1.0/method/Range/i/=3d=3d=3d.html)

範囲に含まれているかどうかを判定。
Range#include? と同じ。

#### サンプルコード
~~~ruby
(1..10).each do |e|
  case e
  when 1..3 then p "match 1..3 : #{e}"
  when 4..6 then p "match 4..6 : #{e}"
  when 7..9 then p "match 7..9 : #{e}"
  else p "no match : #{e}"
  end
end
~~~

#### 出力
~~~
"match 1..3 : 1"
"match 1..3 : 2"
"match 1..3 : 3"
"match 4..6 : 4"
"match 4..6 : 5"
"match 4..6 : 6"
"match 7..9 : 7"
"match 7..9 : 8"
"match 7..9 : 9"
"no match : 10"
~~~

### Regexp#===
[るりま | Regexp#===](http://docs.ruby-lang.org/ja/2.1.0/method/Regexp/i/=3d=3d=3d.html)

正規表現による一致。

#### サンプルコード
~~~ruby
%w(hoge hige hage tanaka tazaki tadokoro not_match).each do |e|
  case e
  when /h.ge/ then p "match /h.ge/ : #{e}"
  when /^ta/ then p "match /ta/ : #{e}"
  else p "no match : #{e}"
  end
end
~~~

#### 出力
~~~
"match /h.ge/ : hoge"
"match /h.ge/ : hige"
"match /h.ge/ : hage"
"match /ta/ : tanaka"
"match /ta/ : tazaki"
"match /ta/ : tadokoro"
"no match : not_match"
~~~

### Proc#===
[るりま | Proc#===](http://docs.ruby-lang.org/ja/2.1.0/method/Proc/i/=3d=3d=3d.html)

Proc(lambda) の処理結果で判定します。

#### サンプルコード
~~~ruby
def fizzbuzz?; ->(v){v % 15 == 0}; end
def buzz?; ->(v){v % 5 == 0}; end
def fizz?; ->(v){v % 3 == 0}; end

(1..30).each do |e|
  case e
  when fizzbuzz? then puts "FizzBuzz"
  when buzz? then puts "Buzz"
  when fizz? then puts "Fizz"
  else p e
  end
end
~~~

#### 出力
~~~
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
~~~

### 自作クラス#===
case の判定条件を自作することもできます

Struct クラスを利用して Person を作成します。
名前と年齢が一致していたら、同一人物と判定します。

#### サンプルコード
~~~ruby
Person = Struct.new(:name, :age, :message) do
  def ===(other)
    name == other.name && age == other.age
  end

  def say
    message
  end
end

def tanaka?; Person.new('tanaka', 32); end
def obokata?; Person.new('obokata', 25); end
def nonomura?; Person.new('nonomura', 40); end
def samuragochi?;Person.new('samuragochi', 41) ; end

[
  Person.new('tanaka', 32, "田中です"),
  Person.new('suzuki', 35),
  Person.new('sato', 35),
  Person.new('samuragochi', 41, "きこえてます"),
  Person.new('obokata', 25, "STAP細胞はあります"),
  Person.new('nonomura', 40, "少子化問題、高齢ェェエエ者ッハアアアァアーー！！")
].each do |e|
  case e
  when tanaka? then puts "match tanaka #{e.say}"
  when obokata? then puts "match obokata #{e.say}"
  when nonomura? then puts "match nonomura #{e.say}"
  when samuragochi? then puts "match samuragochi #{e.say}"
  else puts "あんた誰？ #{e}"
  end
end
~~~

#### 出力
~~~
match tanaka 田中です
あんた誰？ #<struct Person name="suzuki", age=35, message=nil>
あんた誰？ #<struct Person name="sato", age=35, message=nil>
match samuragochi きこえてます
match obokata STAP細胞はあります
match nonomura 少子化問題、高齢ェェエエ者ッハアアアァアーー！！
~~~

### Module#===
[るりま | Module#===](http://docs.ruby-lang.org/ja/2.1.0/method/Module/i/=3d=3d=3d.html)
obj.kind_of?(self) が true の場合、 true を返します。

#### サンプルコード

~~~ruby
class Parent;end
module Extendable;end
class ChildExtendParent < Parent; end
class ChildIncludeExtendable
  include Extendable
end
class Bochi; end

[
  Parent.new,
  ChildExtendParent.new, 
  ChildIncludeExtendable.new, 
  Bochi.new,
  String.new
].each do |e|
  case e
  when Parent then p "match Parent : #{e.class}"
  when Extendable then p "match Extendable : #{e.class}"
  when Bochi then p "match Bochi : #{e.class}"
  else p "no match : #{e.class}"
  end
end
~~~

#### 出力

~~~
"match Parent : Parent"
"match Parent : ChildExtendParent"
"match Extendable : ChildIncludeExtendable"
"match Bochi : Bochi"
"no match : String"
~~~

### 複数クラス混在の比較
#### サンプルコード
~~~ruby
Person = Struct.new(:name, :age, :message) do
  def ===(other)
    return false unless other.is_a? Person
    name == other.name && age == other.age
  end

  def say
    message
  end
end

def obokata?; Person.new('obokata', 25); end
def one_to_ten?; (1..10); end
def string?; "string"; end
def regexp?; /h.ge/; end

[
  1, 10, 11,
  Person.new('tanaka', 32, "田中です"), Person.new('obokata', 25, "STAP細胞はあります"),
  "string", "not string",
  "hoge", "hige", "hage", "not hoge"
].each do |e|
  case e
  when one_to_ten? then puts "1 to 10 : #{e}"
  when obokata? then puts "match obokata : #{e.say}"
  when string? then puts "match string : #{e}"
  when regexp? then puts "match regexp : #{e}"
  else puts "no match : #{e}"
  end
end
~~~

#### 出力
~~~
1 to 10 : 1
1 to 10 : 10
no match : 11
no match : #<struct Person name="tanaka", age=32, message="田中です">
match obokata : STAP細胞はあります
match string : string
no match : not string
match regexp : hoge
match regexp : hige
match regexp : hage
match regexp : not hoge
~~~

## 参考資料
[ruby-list 10525](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/10525)