==とequal?とeql?と===の違い

前回のやつでArray#-を独自定義したクラスで使うためにはeql?メソッドのオーバーロードが必要そうなのだけど、いろいろ似たようなのがあって紛らわしいのでRubyリファレンスで調べてみた。

Object#==

self==(other)

self と other が等しければ真を返します。デフォルトでは equal? と同じ効果です。

このメソッドは各クラスの性質に合わせて再定義するべきです。

と一般的にはこれをオーバーロードするのが良いらしい。

Object#equal?

equal?(other)

other が self 自身の時、真を返します。

このメソッドを再定義してはいけません。

ということで再定義は不可らしい。

irbで実験してみると

irb(main):001:0> a = "hoge"
=> "hoge"
irb(main):002:0> b = "hoge"
=> "hoge"
irb(main):003:0> a == b
=> true
irb(main):004:0> a.equal? b
=> false
irb(main):005:0> b.equal? a
=> false

ということでObject#object_idを見て比較しているものと思われます。

eql?

それで、次が問題のeql?なわけですが、

eql?(other)

二つのオブジェクトが等しければ真を返します。Hash で二つのキーが等しいかどうかを判定するのに使われます。

このメソッドを再定義した時には Object#hash メソッドも再定義しなければなりません。

eql? のデフォルトの定義は equal? と同じくオブジェクトの同一性判定になっています。

ということでObject#hashをみて比較しているようです。よく分からないのでObject#hashを調べてみると、

hash

オブジェクトのハッシュ値を返します。Hash クラスでオブジェクトを格納するのに用いられています。

A.eql?(B) が成立する時は必ず A.hash == B.hash も成立しなければいけません。eql?を再定義した時には必ずこちらも合わせて再定義してください。

デフォルトでは、Object#__id__ と同じ値を返します。ただし、Fixnum, Symbol, String だけは組込みのハッシュ関数が使用されます(これを変えることはできません)。

hash を再定義する場合は、一様に分布する任意の整数を返すようにします。

ということでチェックの厳しさではeql? > == ということでよいのかな…。

「The Ruby Way」のp.251によると、

eql?は、==と同様に、レシーバと引数を比較しますが、こちらの方がわずかに几帳面です。たとえば、==を使って比較する場合、異なる数値オブジェクトは強制的に共通のタイプに変換されますが、eql?では、異なるタイプの数字に対する等価性のテストは行なわれません。

型が異なると即座にfalseが返されることから、引数にレシーバと同じオブジェクトを取るようなメソッドの場合にeql?を使うのが良さそうです。

Object#===

self===(other)

このメソッドは case 文での比較に用いられます。デフォルトは Object#== と同じ働きをしますが、この挙動はサブクラスで所属性のチェックを実現するため適宜再定義されます。

クラスのインスタンスかどうかチェックするときに使うそうです。

なんか分かったような分からないような…。。。