変数同士が等しいかどうかRubyで調べる手段をご存知でしょうか。
数値同士、文字列同士なら言わずと知れた==
を使えばいいですね。ほかにもRubyにはeql?メソッドなどが存在します。
ですが、すべて同じ結果を返すわけではありません。何をもって等しいとするかの定義が少しずつ異なります。
"str" == "str" #=> true "str".equal?("str") #=> false
そこで今回は4つの「等しい」について書いていきます。
equal?メソッド
equal?
メソッドは比較する二つが全く同じオブジェクトであることをチェックします。
全く同じとは、参照しているオブジェクトが同じobject_idを持つかどうか、すなわち、ポインタが同じメモリ上のアドレスを指しているかどうかです。
スポンサーリンク
a = "str" b = a c = a.dup a.equal?(b) #=> true a.equal?(c) #=> false
ここでb = a
はbがaと同じオブジェクトを参照するというのに対し、c = a.dup
は中身がaと同じで全く新しいオブジェクトを作っています。
したがって、bはaと全く同じになりますが、cは同じ値なだけで別のオブジェクトになり、equal?メソッドはfalseを返します。
equal?メソッドは既存のクラスで使われているため、オーバーライドして異なる実装を与えるのはよくありません。
両者が等しいかどうかは次の==
演算子を使います。
==
演算子
プログラミングではお馴染みですが、数値オブジェクトが同じ値かどうか、文字列オブジェクトでは同じ文字列かどうかを比較します。
1 == 1.0 #=> true "str" == "str" #=> true
これらはFixnumクラスやStringクラスで独自の実装を与えられていますが、デフォルトの実装、すなわち独自で定義したクラスのようにObjectクラスを継承しているクラスではequal?メソッドと同じ実装になっています。
なので、
class User attr_accessor :name, :age def initialize(name, age=0) @name = name @age = age end end a = User.new("user1") b = User.new("user1") a == b #=> false
この例ではUserクラスの==
演算子がデフォルトの実装のままのため、内部でequal?メソッドが使われています。
したがって、別のインスタンスを指すa,bは==
演算子ではfalseになってしまいます。
もちろんこれはあまりうれしくないので、==
を使いたいときはオーバーライドして何をもって等しいかを与えてあげる必要があります。
なので、
class User attr_accessor :name, :age def initialize(name, age=0) @name = name @age = age end def ==(v) v.respond_to?(:name) && name == v.name end end a = User.new("user1") b = User.new("user1") a == b #=> true
Userクラスに名前が同じときは等しいとみなすという実装をしました。
respond_to?
メソッドは引数の名前のメソッドが呼び出せるかどうかをチェックしています。これはvがnameメソッドを持たないクラスのインスタンスが来た時用です。受け付ける範囲は狭くなりますが、v.class == "User"
とかでも大丈夫です。
あるいは独自の例外を作っておいて、v.respond_to?(:name)
がfalseの時には例外を発生させるのもいいですね。
eql?メソッド
次はeql?メソッドについてです。
eql?メソッドはHashクラスがキーとして使われるオブジェクトが等しいかどうかをチェックするときに使います。
Rubyではハッシュのキーに文字列やシンボル意外に任意のオブジェクトを使うことができるので、例えば上記のUserクラスのインスタンスa,bを使って、
hash = {a => 1} hash[b] = 2
とすることもできます。
では、このhashは結果どうなるでしょうか?実は次のようになります。
{#<User:0x000000033484a8 @name="user1", @age=0>=>1, #<User:0x000000033deb10 @name="user1", @age=0>=>2}
eql?メソッドではデフォルトでequal?メソッドと同じ比較を行います。なので、
c = a
hash[c] = 3
とすると、cはaと同じオブジェクトなのでハッシュの一つ目の値が3に更新されます。
{#<User:0x000000033484a8 @name="user1", @age=0>=>3, #<User:0x000000033deb10 @name="user1", @age=0>=>2}
ただキーとして使うにはequal?メソッドは若干厳しすぎるので独自の実装を与える必要があります。
それでは、独自実装し、ハッシュのキーとしてオブジェクトを使いたい場合にはどうすればいいでしょうか。
独自実装を行うためには、eql?メソッドとhashメソッドを定義してあげる必要があります。
Hashクラスは、キーオブジェクトのhashメソッドを呼び出しハッシュ値を取得してキー検索を行います。
"str".hash #=> -60268345
hashメソッドは同じキーとしたいオブジェクトが同じ値を返すように実装する必要があります。
逆に異なるキーオブジェクトであるのに同じ値を返してしまう時があります。これは衝突と呼ばれており、衝突を避けるためにeql?メソッドを使います。
実際に独自実装をする場合には内部データの何かしらに委譲してしまうのが簡単かと思います。今回はnameが同じであれば同じキーとする実装にしてみました。
class User attr_accessor :name, :age def initialize(name, age=0) @name = name @age = age end def ==(v) v.respond_to?(:name) && name == v.name end def hash name.hash end def eql?(other) name.eql?(other.name) end end
===
演算子
最後は===
演算子です。
見慣れない演算子ですが、これはcase式の内部で使われている比較演算子です。
例えば、以下のcaseを見てみます
a = "str" case a when "ste" p "ste" when 1 p 1 else p "not match!"
これをif文で直すと次のようになります
a = "str" if "ste" === a p "ste" elsif 1 === a p 1 else p "not match!" end
実は==
演算子ではなく、===
が使われているのです。
ここで注意ですが、if文に書き直した方を見てみると比較対象("ste"や1)が演算子の左に来ています。
というのも、===
は比較対象それぞれのクラスで実装されているものを使うという意味なのです。
rubyでは比較演算子などの特別な演算子にはメソッド名の前にスペースを付けることができるので、1 === a
というのは要するに1.===(a)
と同じものを意味しています。
===
はメソッド名です。
では、==
と===
は何が異なるのでしょうか。
文字列クラスや数値クラスでは実は全く同じ実装になっています。
しかし、Regexpクラスでは実装が異なっています。
a = Regexp.new(/er/) b = Regexp.new(/er/) a == b #=> true a === b #=> false a === "user" #=> true
面白いことに、Regexpクラスでは同じ正規表現かどうかチェックするのではなく、右辺の文字列が正規表現にマッチするかどうかチェックします。
なので、caseでは次のようなことができます。
a = "user" case a when /or/ p "○○or" when /er/ p "○○er" else p "not match!!" end
もしいくつかの解説書にあるようにcaseが==
を使ったif文の単なる書き換えであればこれは説明がつかないことです。
大抵の場合は気を付ける必要はありませんが、caseが==
ではなく、===
を使っているというのは意識しておく必要がありそうです。
- 作者: Peter J. Jones,arton,長尾高弘
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/09
- メディア: 大型本
- この商品を含むブログ (13件) を見る
Ruby on Rails 5アプリケーションプログラミング
- 作者: 山田祥寛
- 出版社/メーカー: 技術評論社
- 発売日: 2017/04/14
- メディア: 大型本
- この商品を含むブログを見る
プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで (Software Design plusシリーズ)
- 作者: 伊藤淳一
- 出版社/メーカー: 技術評論社
- 発売日: 2017/11/25
- メディア: 大型本
- この商品を含むブログを見る