🐙
[Misc #20509] Array#==(other) は other の定義によって挙動が変わるのでそれを明文化したいというチケット
[Misc #20509] Document importance of #to_ary and #to_hash for Array#== and Hash#==
-
Array#==(other)
ではother
の定義によって挙動が変わるのでそれを明文化したいというチケット - どう挙動が変わるのかというと
other.to_ary
とother.==
が定義されている場合に依存します - どういうことかというと
[1, 2, 3] == other
のときに『other.to_ary
メソッドが定義されていればother == [1, 2, 3]
を呼び出す』という挙動になります - 例えば
other.==
が定義されているだけではそのメソッドは特に呼び出されません
class X
# このメソッドは呼び出されない
def ==(other)
pp "X#==(#{other})"
[1, 2, 3] == other
end
end
x = X.new
pp [1, 2, 3] == x
# => false
- しかし
other.to_ary
が定義されている場合はother.==([1, 2, 3])
メソッドが呼び出されて、その結果が返ってきます
class X
# to_ary メソッドが定義されていればこのメソッドが呼び出されてその結果が変えてくる
def ==(other)
pp "X#==(#{other})"
[1, 2, 3] == other
end
# このメソッドは実際には呼び出されない
def to_ary(other)
pp "X#to_ary(#{other})"
[]
end
end
x = X.new
pp [1, 2, 3] == x
# => true
__END__
output:
"X#==([1, 2, 3])"
true
- ただし
#to_ary
は『メソッドが定義されているかどうかの判定』に使われているだけなので実際にメソッドは呼び出されません - 逆にいうと
#to_ary
が定義されているだけでArray
との比較は行われないことになります
class X
def to_ary
[1, 2, 3]
end
end
x = X.new
# [1, 2, 3] == x は #to_ary を内部で呼び出すとかはない
pp [1, 2, 3] == x # => false
pp [1, 2, 3] == x.to_ary # => true
-
#to_ary
を定義した上でArray#==(other)
でも動くようにする場合は以下のように定義する必要があります
class X
# to_ary の結果と比較するような演算子を定義する
def ==(other)
to_ary == other
end
def to_ary
[1, 2, 3]
end
end
x = X.new
pp [1, 2, 3] == x # => true
pp [1, 2, 3] == x.to_ary # => true
- この挙動は
Array#==
だけではなくてHash#==
も同様の挙動になっています - なぜこういう挙動になっているのかの背景は以下のコメントに詳しく書いてあります
- https://bugs.ruby-lang.org/issues/20509#note-3
- CRuby の実装的に対象のオブジェクトが『配列かどうか』を内部で判定するときに『
#to_ary
が定義されていれば配列オブジェクトとして扱う』みたいな感じになっているらしい- いわゆるダックタイピング的な呼び出しを行うための機構
- なので
#to_ary
の結果は関係ない
Discussion