カテゴリー:
Ruby
タグ:
 Ruby

このエントリーをはてなブックマークに追加
更新日時:
2016年11月19日(土)
作成日時:
2016年11月19日(土)

前の記事 / 次の記事

Rubyには

  • ==
  • ===
  • equal?
  • eql?

という似たような、でも調べてみると全然違う比較メソッドたちがあり、
基本的には全て==で代替できるので正確に分かっていなくてもコードは書けてしまうのだけど、
知っているとより直感的に書けることに今更気付いたのでまとめ。

目次

参考

先に結論だけ

先に結論だけ書いてしまうと、

  • 基本的には == メソッド
  • オブジェクトIDを比較したい時は equal? メソッド 、
  • 所属性を確認したい時は === メソッド
  • eql? メソッド は見なかったことにする。

という使い分けで事は足りると思う。

同一性、同値性、所属性

これらのメソッドたちを理解するためには
同一性同値性所属性 という概念を念頭に置いて考える必要があり、
これらの概念を把握すれば各メソッドの違いは自ずと明らかになると思われるので、
まずこれらの概念について説明する。

同一性

同一のオブジェクトであるかどうか。
同じ値であっても別々のオブジェクトであれば偽となる。
equal? を使って判定。

同値性

同一の値であるかどうか。
同一の値であれば別々のオブジェクトであっても真となる。
== を使って判定。

所属性

所属関係にあるかどうか。
同一性、同値性に関わらず、所属関係にあれば真となる。
=== を使って判定。

次に各具体例を列挙する。

equal? メソッド

同一性 を確認するために使う。

同じオブジェクトなら真

last_name   = String.new("Tanaka")
family_name = last_name

last_name.equal? last_name     #=> true
family_name.equal? family_name #=> true

値が同じでも違うオブジェクトなら(オブジェクトIDが異なれば)偽

last_name   = String.new("Tanaka")
family_name = String.new("Tanaka")

last_name.object_id   => 69858421892800
family_name.object_id => 69858421892760

last_name.equal? family_name #=> false
family_name.equal? last_name #=> false

== メソッド

同値性 を確認するために使う。

但し、ほとんどの場合は同値性を確認するために使えるが、
実際どう振る舞うかは各クラスにおいてObject#==(これ自体は同一性を判定)を再定義した際の実装次第なので、
自作クラスで==を再定義していなかったり、変態的な再定義をしていたりするとこの限りではない。

同じ値なら真

last_name   = String.new("Tanaka")
family_name = String.new("Tanaka")

last_name   == last_name   #=> true
family_name == family_name #=> true

同じ値なら違うオブジェクトでも真

last_name   = String.new("Tanaka")
family_name = String.new("Tanaka")

last_name.object_id   => 69858421892800
family_name.object_id => 69858421892760

family_name == last_name   #=> true
last_name   == family_name #=> true

そもそもクラスが違ったとしても値が同じであれば真

fixnum = 2
float  = 2.0

fixnum.class #=> Fixnum
float.class  #=> Float

fixnum == float  #=> true
float  == fixnum #=> true

=== メソッド

所属性 を確認するために使う。

所属関係があれば真(右辺が左辺に含まれていれば真)

但し、多態(ポリモルフィズム)が活用されているので、
===メソッドを受け取るオブジェクトによって真になる条件が異なる。

左辺がモジュールオブジェクト(左辺がクラス)

【右辺.is_a?(左辺)であれば真】

ここで言うモジュールオブジェクトというのは、
クラスとモジュールという文脈においてのモジュールではなく、
標準クラスであるモジュールクラスとしてのモジュールのこと。

BasicObjectクラスとObjectクラスを除く全てのクラスはモジュールオブジェクトを継承しているので、
実際に利用する上では変態的なことをしない限り全てのクラスについて当て嵌まる。

name = String.new("Tanaka")

String    === name   #=> true
last_name === String #=> false

fixnum = 2
float  = 2.0

fixnum.class #=> Fixnum
float.class  #=> Float

Numeric === fixnum #=> true
Numeric === float  #=> true
Float   === fixnum #=> false  
Fixnum  === float  #=> false

左辺が範囲オブジェクト

【左辺.include?(右辺)であれば真】

under_age = 0..19

under_age === 16 #=> true
under_age === 20 #=> false

左辺が正規表現オブジェクト

【左辺 =~ 右辺であれば真】

=~を使った正規表現の比較では正規表現オブジェクトが左辺、右辺どちらにあっても
比較を行うことができるが(Stringクラスにも=~が定義されているため)、
===を使った比較の場合、正規表現オブジェクトは必ず左辺にある必要があるので注意。
(Stringクラスにインスタンスメソッドには===メソッドが定義されていないため。)

regexp_last_name = /Tanaka/

regexp_last_name === "Tanaka" #=> true
regexp_last_name === "Manaka" #=> false
"Tanaka" === regexp_last_name #=> false

左辺が上記のいずれでもない

【左辺 == 右辺 として扱われる】

例えば配列クラス等はinclude?メソッドを備えているので、
範囲オブジェクトと同じ振る舞いをそうしそうだが、
上記以外のオブジェクトには===メソッドが定義されておらず、
その場合は全て==と同じ扱いになるので注意。

last_name   = String.new("Tanaka")
family_name = String.new("Tanaka")

last_name   === family_name #=> true
family_name === last_name   #=> true

fixnum = 2
float  = 2.0

fixnum.class #=> Fixnum
float.class  #=> Float

fixnum === float  #=> true
float  === fixnum #=> true

odd = [1, 3, 5, 7, 9]

odd === 5 #=> false

=== メソッドの使いどころ

主な使いどころはcase文で、case文では内部的に===を使った判定が行われているので、
次のような文を書くことができる。

case object
when String
  puts "objectは文字列です"
when Numeric
  puts "objectは数値です"
when Array
  puts "objectは配列です"
when Hash
  puts "objectはハッシュです"
end

eql? メソッドは?

さて、equal?、==、=== の違いは分かったけれど、eql?メソッドは?というところだけど、
eql? メソッドというのは基本的にハッシュのキーを比較するために内部的に用いられているメソッド、
ということなので、あまり頑張って使いこなさない方が良いのではないかと思っている。

Rubyのリファレンスにも通常は==かequal?を使えと書いてある。

なので、eql? メソッドとは知り合わなかったことにして
基本的には equal?、==、===を使っていくのがいいのではないかと。

とはいえ、調べてしまったので一応その振る舞いを書いておくと、
eql? メソッドは型を考慮した同値性を判定する

eql?メソッドの場合だけ結果が真になるような具体的なケースが思い浮かばないが、
ハッシュキーの比較において、オブジェクトIDは異なっても構わないが、
型と値が同じであることを判定するのに用いられている。

まあ、自作のクラスとかでハッシュキーの判定の際は同一のキーとして扱って欲しい
みたいな時には自作クラスでeql?メソッドをオーバーライドしたらいいのでしょう。

===の扱いが凄くRubyっぽいと思った。