Ruby | case と === の歴史 #ruby
概要
Ruby の case 文と ===
の歴史について
昔の case
昔の Ruby には String 化症候群 というものがあったそうです。
※参考資料(ruby-list 10525)
case の内部では no.to_s =~ 1.to_s
のような比較をしていた。
しかし、何でも文字列で比較するのはおかしいということで変更することになったが、
文字列以外に =~
を利用するのも気持ち悪いので新たな演算子を利用することに。
そこで ===
の登場。
現在へ・・・
現在の case
現在の case 文は内部比較に ===
を利用しています。
これによって、クラスに合わせた比較処理を実現し、
case をポリモーフィックに利用することができます。
サンプル
String#===
文字列の内容で比較します。
サンプルコード
%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#include? と同じ。
サンプルコード
(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#===
正規表現による一致。
サンプルコード
%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(lambda) の処理結果で判定します。
サンプルコード
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 を作成します。
名前と年齢が一致していたら、同一人物と判定します。
サンプルコード
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#===
obj.kind_of?(self) が true の場合、 true を返します。
サンプルコード
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"
複数クラス混在の比較
サンプルコード
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