Why Doesn't Array Override the Triple Equal Sign Method in Ruby

Why doesn't Array override the triple equal sign method in Ruby?

Because it's in the specification.

it "treats a literal array as its own when argument, rather than a list of arguments"

The spec was added February 3, 2009 by Charles Nutter (@headius). Since this answer's probably not what you're looking for, why don't you ask him?

To take a wild and completely uninformed stab in the dark, it seems to me that you might have hit upon the answer by using a splat in your question. Since the functionality is available by design, why duplicate when doing so would remove the ability to test Array equality? As Jordan points out above, situations exist where this is useful.


Future readers should note that unless the array in question is already instantiated, using an array at all is not necessary to match on multiple expressions:

x = 2

case x
when 1, 2, 3 then "match"
else "no match"
end # => "match"

Triple equals versions of Enumerable functions

From the comment of mine -

Looking at your need, I can suggest you one way to meet the need, which you are looking for:

[MyClass, MyOtherClass] & some_object.class.ancestors

Although it is still an wrokaround using Array Intersection.

Array difference by explicitly specified method or block

The "normal" object equality for e.g. Hashes and set operations on Arrays (such as the - operation) uses the output of the Object#hash method of the contained objects along with the semantics of the a.eql?(b) comparison.

This can be used to to improve performance. Ruby assumes here that two objects are eql? if the return value of their respective hash methods is the same (and consequently, assumes that two objects returning different hash values to not be eql?).

For a normal a - b operation, this can thus be used to first calculate the hash value of each object once and then only compare those values. This is quite fast.

Now, if you have a custom equality, your best bet would be to overwrite the object's hash methods so that they return suitable values for those semantics.

A common approach is to build an array containing all data taking part of the object's identity and getting its hash, e.g.

class MyObject
#...
attr_accessor :foo, :bar

def hash
[self.class, foo, bar].hash
end
end

In your object's hash method, you would than include all data that is currently considered by your f comparison method. Instead of actually using f then, you are using the default semantics of all Ruby objects and again can achieve quick set operations with your objects.

If however this is not feasible (e.g. because you need different equality semantics based on use-case), you could emulate what ruby does on your own.

With your f method, you could then perform your set operation as follows:

def f_difference(a, b)
a_map = a.each_with_object({}) do |a_el, hash|
hash[a_el.f] = a_el
end

b.each do |b_el|
a_map.delete b_el.f
end

a_map.values
end

With this approach, you only need to calculate the f value of each of your objects once. We first build a hash map with all f values and elements from a and remove the matching elements from b according to their f values. The remaining values are the result.

This approach saves you from having to loop over b for each object in a which can be slow of you have a lot of objects. If however you only have a few objects on each of your arrays, your original approach should already be fine.

Let's have a look at a benchmark whee I use the standard hash method in place of your custom f to have a comparable result.

require 'benchmark/ips'

def question_diff(a, b)
a.select { |ael| b.all? {|bel| ael.hash != bel.hash} }
end

def answer_diff(a, b)
a_map = a.each_with_object({}) do |a_el, hash|
hash[a_el.hash] = a_el
end

b.each do |b_el|
a_map.delete b_el.hash
end

a_map.values
end

A = Array.new(100) { rand(10_000) }
B = Array.new(10) { A.sample }

Benchmark.ips do |x|
x.report("question") { question_diff(A, B) }
x.report("answer") { answer_diff(A, B) }

x.compare!
end

With Ruby 2.7.1, I get the following result on my machine, showing that the original approach from the question is about 5.9 times slower than the optimized version from my answer:

Warming up --------------------------------------
question 1.304k i/100ms
answer 7.504k i/100ms
Calculating -------------------------------------
question 12.779k (± 2.0%) i/s - 63.896k in 5.002006s
answer 74.898k (± 3.3%) i/s - 375.200k in 5.015239s

Comparison:
answer: 74898.0 i/s
question: 12779.3 i/s - 5.86x (± 0.00) slower

Does the triple equal sign (===) behave differently in AssemblyScript?

Yes. In AssemblyScript tripple equal ("===") compare raw references and skip overloading operator ("=="). See docs.

There are have proposal avoid this non-standard for TypeScript behaviour. You could check and upvote this issue

What does the === operator do in Ruby?

Just like with every other method in Ruby (or actually pretty much any object-oriented language),

a === b

means whatever the author of a's class wants it to mean.

However, if you don't want to confuse the heck out of your colleagues, the convention is that === is the case subsumption operator. Basically, it's a boolean operator which asks the question "If I have a drawer labelled a would it make sense to put b in that drawer?"

An alternative formulation is "If a described a set, would b be a member of that set?"

For example:

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

Integer === 42 # => true
Integer === 'fourtytwo' # => false

/ell/ === 'Hello' # => true
/ell/ === 'Foobar' # => false

The main usage for the === operator is in case expressions, since

case foo
when bar
baz
when quux
flurb
else
blarf
end

gets translated to something (roughly) like

_temp = foo

if bar === _temp
baz
elsif quux === _temp
flurb
else
blarf
end

Note that if you want to search for this operator, it is usually called the triple equals operator or threequals operator or case equality operator. I really dislike those names, because this operator has absolutely nothing whatsoever to do with equality.

In particular, one would expect equality to be symmetric: if a is equal to b, then b better be also equal to a. Also, one would expect equality to be transitive: if a == b and b == c, then a == c. While there is no way to actually guarantee that in a single-dispatch language like Ruby, you should at least make an effort to preserve this property (for example, by following the coerce protocol).

However, for === there is no expectation of either symmetry or transitivity. In fact, it is very much by design not symmetric. That's why I don't like calling it anything that even remotely resembles equality. It's also why I think, it should have been called something else like ~~~ or whatever.

triple equal sign R

You can define an infix operator (with the e in %e% for "equal";-):

`%e%` <- function(a, b) identical(a, b)
3 %e% 3
#[1] TRUE

Or if you want the triple-equal sign as

`%===%` <- function(a, b) identical(a, b)
3 %===% 3
#[1] TRUE

Or an example with vectors

1:3 %===% 1:3
#[1] TRUE

These infix operations (where the operator is used between the operands) can also be written as

`%===%`(1:3, 1:3) 

in the same way that you can write

`==`(3, 3)

What does the Ruby `uniq` method use for equality checking?

It compares values using their hash and eql? methods for efficiency.

https://ruby-doc.org/core-2.5.0/Array.html#method-i-uniq-3F

So you should override eql? (that is ==) and hash

UPDATE:

I cannot explain fully why is that, but overriding hash and == doesn't work. I guess it's cause by the way uniq is implemented in C:

From: array.c (C Method):
Owner: Array
Visibility: public
Number of lines: 20

static VALUE
rb_ary_uniq(VALUE ary)
{
VALUE hash, uniq;

if (RARRAY_LEN(ary) <= 1)
return rb_ary_dup(ary);
if (rb_block_given_p()) {
hash = ary_make_hash_by(ary);
uniq = rb_hash_values(hash);
}
else {
hash = ary_make_hash(ary);
uniq = rb_hash_values(hash);
}
RBASIC_SET_CLASS(uniq, rb_obj_class(ary));
ary_recycle_hash(hash);

return uniq;
}

You can bypass that by using a block version of uniq:

> [Foo.new(1,2), Foo.new(1,2), Foo.new(2,3)].uniq{|f| [f.a, f.b]}
=> [#<Foo:0x0000562e48937cc8 @a=1, @b=2>, #<Foo:0x0000562e48937c78 @a=2, @b=3>]

Or use Struct instead:

F = Struct.new(:a, :b)
[F.new(1,2), F.new(1,2), F.new(2,3)].uniq
# => [#<struct F a=1, b=2>, #<struct F a=2, b=3>]

UPDATE2:

Actually in terms of overriding it's not the same if you override == or eql?. When I overriden eql? It worked as intended:

class Foo
attr_accessor :a, :b

def initialize(a, b)
@a = a
@b = b
end

def eql?(other)
(@a == other.a && @b == other.b)
end

def hash
[a, b].hash
end

def to_s
"#{@a}: #{@b}"
end

end

a = [
Foo.new(1, 1),
Foo.new(1, 2),
Foo.new(2, 1),
Foo.new(2, 2),
Foo.new(2, 2)
]
a.uniq
#=> [#<Foo:0x0000562e483bff70 @a=1, @b=1>,
#<Foo:0x0000562e483bff48 @a=1, @b=2>,
#<Foo:0x0000562e483bff20 @a=2, @b=1>,
#<Foo:0x0000562e483bfef8 @a=2, @b=2>]

How do you override the ruby case equality operator? (===)

The expression after the 'case' keyword is the right hand side of the === expression, and the expression after the 'when' keyword is on the left hand side of the expression. So, the method that is being called is String.===, not A.===.

A quick approach to reversing the comparison:

class Revcomp
def initialize(obj)
@obj = obj
end

def ===(other)
other === @obj
end

def self.rev(obj)
Revcomp.new(obj)
end
end

class Test
def ===(other)
puts "here"
end
end

t = Test.new

case t
when Revcomp.rev("abc")
puts "there"
else
puts "somewhere"
end

Any good reason for Ruby to have == AND eql? ? (similarly with to_a and to_ary)

I don't know the reasoning for this particular choice in ruby, but I'll just point out that equality is a difficult concept.

Common Lisp, for example has eq, eql, equal, equalp, and for that matter =

It can be very useful to be able to tell the difference between two references to the same object, two different objects of the same type with the same value, two objects with the same value but of different types, etc. How many variations make sense depends on what makes sense in the language.

If I recall it correctly (I don't use ruby), rubys predicates are implementing three of these cases

== is equality of value

eql? is equality of value and type

equal? is true only for the same object



Related Topics



Leave a reply



Submit