Thursday, November 15, 2007

What Is Threequals (===)?

There was a post on ruby-talk recently, demanding to know why these statements don't display the associative property you would normally expect of equality:

(1..5) === 3 # => true
3 === (1..5) # => false


The answer is simple: ===, aka threequals, isn't an equality operator - it's a method, and it's used for weird corner cases connected to but not exactly corresponding to equality. Module#=== tests whether an object is an instance of self or any of self's subclasses. Object#=== is a synonym for Object#==. Likewise String#=== is a synonym for String#==, while Regexp#=== is a synonym for Regexp#=~.

Range#=== tests for inclusion; range === some_object returns true when that object falls within the range. For example:

>> ('a'..'c') == 'b'
=> false
>> ('a'..'c') === 'b'
=> true


So (1..5) === 3 is true because 3 is in that range, but 3 isn't equal to that range, so 3 === (1..5) doesn't return true. Logical enough. If you're wondering how you're supposed to remember all this, you aren't. You don't really even need to know it. The === method exists for controlling how a case/when block will evaluate an object. It should never be used by humans. It is code written to be consumed by other code, specifically, by case and when.

Delving into the inner workings of Ruby is lots of fun if you know what you're doing and a recipe for trouble if you don't. (If you sort of know what you're doing, but not totally, then it's a recipe for fun trouble.)

Just to illustrate what can go wrong, here's one of many ways to drive IRB insane:

>> def ==(object)
>> puts "asdf"
>> end
asdf
asdf
asdf
asdf
=> nil
asdf
>> true == false
asdf
asdf
asdf
asdf
asdf
asdf
asdf
asdf
asdf
asdf
asdf
asdf
asdf
asdf
=> nil