77

In Ruby, what is the difference between == and ===? The RDoc says

Case Equality—For class Object, effectively the same as calling #==, but typically overridden by descendents to provide meaningful semantics in case statements.

Is #== the same as ==? And could you provide an example of when/how this is used in case statements?

Mads Mobæk
  • 34,762
  • 20
  • 71
  • 78

3 Answers3

141

The two really have nothing to do with each other. In particular, #== is the equality operator and #=== has absolutely nothing to with equality. Personally, I find it rather unfortunate that #=== looks so similar to #==, uses the equals sign and is often called the case equality operator, triple equals operator or threequals operator when it really has nothing to do with equality.

I call #=== the case subsumption operator (it's the best I could come up with, I'm open to suggestions, especially from native English speakers).

The best way to describe a === b is "if I have a drawer labeled a, does it make sense to put b in it?"

So, for example, Module#=== tests whether b.is_a?(a). If you have Integer === 2, does it make sense to put 2 in a box labeled Integer? Yes, it does. What about Integer === 'hello'? Obviously not.

Another example is Regexp#===. It tests for a match. Does it make sense to put 'hello' in a box labeled /el+/? Yes, it does.

For collections such as ranges, Range#=== is defined as a membership test: it makes sense to put an element in a box labeled with a collection if that element is in the collection.

So, that's what #=== does: it tests whether the argument can be subsumed under the receiver.

What does that have to with case expressions? Simple:

case foo
when bar
  baz
end

is the same as

if bar === foo
  baz
end
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • 4
    `Array#===` is not defined as membership in ruby 1.8 or 1.9.1. `Range#===` is though. – sepp2k Aug 06 '10 at 09:09
  • 2
    @sepp2k: You're right. That's what I get for assuming sensible semantics without checking the documentation first. – Jörg W Mittag Aug 06 '10 at 09:13
  • This is new to me. Good to know! – Karl Aug 06 '10 at 19:10
  • 15
    "if I have a drawer labeled a, does it make sense to put b in it?". Splendid image. – tokland Dec 16 '10 at 19:26
  • 1
    What's the etymology of this operator? Was it used like this in Perl or CLU, or something made up new by Matz? – Andrew Grimm Dec 17 '10 at 05:59
  • how about "is it inside?" operator. I promise not many english speakers have heard that word "subsumption" – BenKoshy Sep 23 '16 at 05:20
  • 1
    @BKSpurgeon: Really? I'm not even a native speaker, and I know that word. And I see it quite commonly used. Especially in programming. For example in the famous Jigsaw paper: "Mixin based inheritance subsumes other forms of linear multiple inheritance typical of LISP based object oriented languages" and from the same paper "Just as modules and interfaces subsume some functions supported by linkers, inheritance subsumes some functions of text editors". – Jörg W Mittag Sep 23 '16 at 07:40
  • 1
    From the even more famous Typeful Programming paper: "Since `myVehicle:Vehicle` and `Vehicle<:Object`, we have that `myVehicle:Object` (this is called the *subsumption rule*). Hence functions expecting objects will accept vehicles" and "Here `ColorPoint <: Point`, hence any program working on points will also accept color points, by subsumption.", " Subtyping increases flexibility in typing via subsumption, but by the same mechanism can cause loss of type information.", and there's a typing rule called "Subsumption". The Anchored Exception Declarations paper, which probably most Java … – Jörg W Mittag Sep 23 '16 at 07:43
  • 1
    … programmers are familiar with contains that word, the Diamondback paper which probably most Ruby programmers are familiar with contains that word, the DOT paper, which probably most Scala programmers are familiar with contains that word, he Scala Language Specification uses it, and so on. – Jörg W Mittag Sep 23 '16 at 07:47
  • Suggestion for a better name, "pattern matching operator" and then `pattern === value` – akuhn Jan 27 '17 at 13:35
  • To avoid confusion in a code base, I can guess we should avoid using `===`, right? – Yuri Ghensev Jun 21 '17 at 20:58
  • @BSeven It being "not a very common word" could be an *advantage*, since it's then less easily confused with something else :-) – Stefan Pochmann Nov 29 '17 at 00:22
11

Yes, by #== the docs mean "the instance method == of the current object".

=== is used in case statements as such:

case obj
when x
  foo
when y
  bar
end

Is the same as

if x === obj
  foo
elsif y === obj
  bar
end

Some classes that define their own === are Range (to act like include?), Class (to act like obj.is_a?(klass)) and Regexp (to act like =~ except returning a boolean). Some classes that don't define their own === are the numeric classes and String.

So

case x
when 0
  puts "Lots"
when Numeric
  puts(100.0 / x)
when /^\d+$/
  puts(100.0 / x.to_f)
default
  raise ArgumentError, "x is not a number or numeric string"
end

is the same as

if 0 == x
  puts "Lots"
elsif x.is_a? Numeric
  puts(100.0 / x)
elsif x =~ /^\d+$/
  puts(100.0 / x.to_f)
else
  raise ArgumentError, "x is not a number or numeric string"
end
sepp2k
  • 363,768
  • 54
  • 674
  • 675
  • Curious if you were to put a string in the when statement, would it be similar to saying `case x; when string --> if "string" == x`? – the12 Nov 10 '16 at 05:38
  • 1
    @the12 Are you asking whether Ruby will automatically add quotes around an identifier or was that a typo? Anyway `case x; when string` is equivalent to `if string === x`, which, if `string` contains a string, is equivalent to `if string == x`. Likewise `case x; when "string"` is equivalent to `if "string" === x` and `if "string" == x`. – sepp2k Nov 10 '16 at 16:02
5

Fun fact, === is also used to match exceptions in rescue

Here is an example

class Example
  def self.===(exception)
    puts "Triple equals has been called."
    true
  end
end

raise rescue Example
# => prints "Triple equals has been called."
# => no exception raised

This is used to match system errors.

SystemCallError.=== has been defined to return true when the two have the same errno. With this system call errors with the same error number, such as Errno::EAGAIN and Errno::EWOULDBLOCK, can both be rescued by listing just one of them.

akuhn
  • 27,477
  • 2
  • 76
  • 91