Ruby の ===

Ruby3.3リファレンスマニュアルで主要なクラスだけ調べた。

経緯

昨日の「勝手にモブプロ」で、Rubyにも===あるよねって話が出てた。
そうだっけ?なにするんだっけ?って思ったので調べた。

結論

Objectクラスで===は定義されている。デフォルトはObjectクラスの==と同じ。
=====は、各クラスの性質に合わせて各クラスで再定義されている。

=====と異なる意味で再定義されている主要なクラス↓
・Range
Regexp
・Proc
・Module

case when式when===を呼び出している。

詳細

Objectクラスの == と ===

根本的なところ

==

Object#== (Ruby 3.3 リファレンスマニュアル)

オブジェクトと other が等しければ真を返します。
デフォルトでは equal? と同じオブジェクトの同一性判定になっています。

つまりobject_idが等しければtrueを返す。

このメソッドは各クラスの性質に合わせて再定義すべきです。多くの場合、オブジェクトの内容が等しければ真を返すように(同値性を判定するように)再定義されることが期待されています。

この部分は下記のおまけに記載。

===

Object#=== (Ruby 3.3 リファレンスマニュアル)

デフォルトでは内部で Object#== を呼び出します。

==と同じくobject_idが等しければtrueを返す。

case 式で使用されるメソッドです。制御構造/case も参照してください。

case式に関しては以下。
制御構造 (Ruby 3.3 リファレンスマニュアル)

case whenをifにすると===がみえてくる

case 式0
when 式1, 式2
  stmt1
when 式3, 式4
  stmt2
else
  stmt3
end

は以下とほぼ等価

_tmp = 式0
if 式1 === _tmp or 式2 === _tmp
  stmt1
elsif 式3 === _tmp or 式4 === _tmp
  stmt2
else
  stmt3
end


=== が ==と異なる再定義をしているもの

Range

Range#=== (Ruby 3.3 リファレンスマニュアル)
始端と終端のなかにあるときにtrueを返す。
内部ではRange#cover?が実行されている。
(2.5以前はRange#include?だった)

(1..10) === 10     #=> true
(1...10) === 10    #=> false

("a".."b").include?("a")    #=> true
("a".."b").include?("ab")   #=> false
("a".."b").cover?("ab")     #=> true
("a".."b") === "ab"         #=> true
Regexp

Regexp#=== (Ruby 3.3 リファレンスマニュアル)
文字列との正規表現マッチを行う。マッチしたときはtrueを返す。

/\A[a-z]*\z/ === "HELLO"  #=> false
/\A[A-Z]*\z/ === "HELLO"  #=> true

"HELLO" === /\A[A-Z]*\z/  #=> false
#逆はダメなので注意
Proc

Proc#=== (Ruby 3.3 リファレンスマニュアル)
Procオブジェクトを実行して結果を返す。

proc = Proc.new{ | a , b | a * b }

proc.call(2, 5)     #=> 10
proc[2, 5]          #=> 10
proc === [2, 5]     #=> 10
Module

Module#=== (Ruby 3.3 リファレンスマニュアル)
指定のオブジェクトがそのクラスかそのサブクラスのインスタンスであるときtrueを返す。

class Mikan < Object
end

class Arita < Mikan
end

mikan = Mikan.new
arita = Arita.new

Object === mikan   #=> true
Mikan === mikan    #=> true
Arita === mikan    #=> false
Object === arita   #=> true
Mikan === arita    #=> true
Arita === arita    #=> true

mikan === Mikan    #=> false
#逆はダメなので注意

おまけ

== は 人の目で同じならtrue、に再定義がほとんどっぽい

たしかにequal?==がちょっと意味が違うクラスがほとんど。
人の目でみて同じならtrue、という意味に再定義されている。

Integer

Integer#== (Ruby 3.3 リファレンスマニュアル)

1 == 2            #=> false
1 == 1.0          #=> true
1.equal?(1.0)     #=> false

1はIntegerクラスで1.0はFloatクラスだが、==はtrueになる

Array

Array#== (Ruby 3.3 リファレンスマニュアル)

array = [ "a", "c", 7 ]

array == [ "a", "c" ]             #=> false
array == [ "a", "c", 7 ]          #=> true
array.equal?([ "a", "c", 7 ])     #=> false
Hash

Hash#== (Ruby 3.3 リファレンスマニュアル)

hash = { 1 => :a, 2 => :b }

hash == { 1 => :a }               #=> false
hash == { 2 => :b , 1 => :a }     #=> true
hash == { 2 => :b , 1.0 => :a }   #=> false 

{ :x => 1 } == { :x => 1.0 }      #=> true 
Range

Range#== (Ruby 3.3 リファレンスマニュアル)

range = (1..2)

range == (1..2)                #=> true
range == (1...2)               #=> false
(1..2) == Range.new(1.0, 2.0)  #=> true
range.equal?(1..2)             #=> false
Regexp

Regexp#== (Ruby 3.3 リファレンスマニュアル)

a = /\d\d\d/

a == /\d\d\d/    #=> true
a == /\d{3}/     #=> false
a.equal?(/\d\d\d/)   #=> false

終わりに

るりま読んでこれ書いたけど、チェリー本にがっつり書いてあった…。

===がどのように定義されているか分かるとwhen節の条件部に何を書くか明確にできる。(気がする)

完全未経験から現在フィヨルドブートキャンプで学習中。
自分で気になったところの記録(メモ)です。
間違い等あればご指摘いただければ幸いです。

参考リンク

library _builtin (Ruby 3.3 リファレンスマニュアル)