LoginSignup
15
12

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-11-20

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

概要

Ruby の case 文と === の歴史について

昔の case

昔の Ruby には String 化症候群 というものがあったそうです。
※参考資料(ruby-list 10525)

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

現在の case

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

サンプル

String#===

るりま | 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#===

範囲に含まれているかどうかを判定。
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#===

るりま | 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#===

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

参考資料

ruby-list 10525

15
12
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
12