Edited at

Ruby における ===


追記


  • 2018-12-25 Ruby 2.6 における Range の仕様変更について追記。


概要

case 式で利用されるメソッド。 Case equality operator (case等価演算子) とも呼ばれる。

case 5

when String # String === 5 が呼ばれる。
when /foo/ # /foo/ === 5 が呼ばれる。
when 1..10 # (1..10) === 5 が呼ばれる。
end

また、 Enumerable のメソッドのうち、 pattern を引数とするメソッドにも使われる。

[1, 2, 3, 4, 5].grep(2..4) # それぞれの要素に対して (2..4) === element が呼ばれる。


  • 他のプログラミング言語 (JavaScript や PHP 等) にみられるような、厳密等価演算子、およびそれに類するものではない。

  • クラスごとに === の定義をオーバーライドできる。

  • (=== に限ったことではないが) 演算子の前後を逆にすると、結果が変わる場合があるため注意。



    • /foo/ === 'foo' は true だが、 'foo' === /foo/ は false。


      • 前者は Regexp#=== を呼び出しているのに対し、後者は String#=== を呼び出している。

      • Ruby に慣れていないと間違えやすい印象。



    • 基本的に、演算子の前後の値のクラスが異なる場合、前後を逆にするとまず結果が変わる。


      • クラスが同じであっても、 IPAddr.new('127.0.0.1/8') === IPAddr.new('127.0.0.1/32') が true で IPAddr.new('127.0.0.1/32') === IPAddr.new('127.0.0.1/8') が false であるように、クラスの === の定義によって挙動は様々。





  • RuboCop のスタイルガイドでは、 === を直接使わないよう言及されている




特徴的な動きをする ===

when 1..10 のように書けるよう、 === に 包括関係の検査 を充てているものが多い。

※もちろん、クラスによっては別の意味を充てていることもある。


Range#===


<= 2.5.x

Range#include? と同等。

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

(1..10).include?(5) # ↑と同等。


= 2.6.0

Range#include?Range#cover? を合わせたような、説明しづらい挙動になった。

該当のコミット: range.c: === by cover?

# 文字列の Range であれば、 2.5.x 以前のまま。

('b'..'d').cover?('ba') # 2.5.x でも 2.6.0 でも true
('b'..'d') === 'ba' # 2.5.x でも 2.6.0 でも false

# 2.6.0 で Range#cover? は Range を受け付けるようになったが、 Range#=== では受け付けないまま。
(1..4).cover?(2..3) # 2.5.x では false 、 2.6.0 では true
(1..4) === (2..3) # 2.5.x でも 2.6.0 でも false

従来、数値 (として内部的に扱える値) でも文字列でもない Range の include?Enumerable#include? を利用していた。

(各要素と == で比較し、 true であるものがあるかどうかの検査)

それが Range#cover? (<=> による比較) に変わることで、一部のケースでより納得しやすい結果になるようになった。

(Date.today..Date.today + 1).cover?(DateTime.now) # 2.5.x でも 2.6.0 でも true

(Date.today..Date.today + 1) === DateTime.now # 2.5.x では false 、 2.6.0 では true

現状、正確な挙動を把握するにはソースを読むしかない。

2.6.0 対応のるりまが待たれる。


Regexp#===

Regexp#match(str)Regexp#match?(str) の中間的な動作をする。

Regexp#match? は Ruby 2.4 から。


  • 返り値は bool (match? と同じ)

  • 組み込み変数 $~ などを変更する (match と同じ)

/foo/ === 'foo' # => true

$~ # => #<MatchData "foo">

/bar/.match('bar') # => #<MatchData "bar">
$~ # => #<MatchData "bar">

/baz/.match?('baz') # => true
$~ # => #<MatchData "bar"> ← match? は $~ を変更しない。


Module#===

Object#is_a? と同等。

String === 'foo'    # => true

'foo'.is_a?(String) # ↑と同等。


Method#=== Proc#===

それぞれ Method#call(*args) Proc#call(*args) (メソッド/Proc の実行) と同等。

Method#=== は Ruby 2.5 から。

f = ->(v) { v * 10 }

f === 2 # => 20
f.call(2) # ↑と同等。


Set#=== (Ruby 2.5)

Set#include? と同等。

require 'set'

Set[1, 2, 3] === 2 # => true
Set[1, 2, 3].include?(2) # ↑と同等。


IPAddr#===

IPAddr#include? と同等。

require 'ipaddr'

IPAddr.new('127.0.0.0/8') === '127.0.0.1' # => true
IPAddr.new('127.0.0.0/8').include?('127.0.0.1') # ↑と同等。