В чем разница между равными ?, eql ?, === и ==?

552

Я пытаюсь понять разницу между этими четырьмя методами. Я знаю по умолчанию, что ==вызывает метод, equal?который возвращает истину, когда оба операнда ссылаются на один и тот же объект.

===по умолчанию также вызывает, ==который вызывает equal?... хорошо, так что, если все эти три метода не переопределены, то я думаю ===, ==и equal?делать то же самое?

Сейчас приходит eql?. Что это делает (по умолчанию)? Делает ли он вызов хеша / идентификатора операнда?

Почему в Ruby так много знаков равенства? Должны ли они отличаться по семантике?

Denniss
источник
Я только начал IRB и имел следующий результат , который противоречит вашим ... Все эти 3 условия: "a" == "a", "a" === "a"и "a".eql? "a". Но это неверно: "a".equal? "a"(Мой - ruby ​​1.9.2-p180)
PeterWong
7
@Peter: Это потому, что строки переопределяют все операторы равенства. Попытка использования , a = Object.new; b = Object.newто все ==, ===, .equal?, .eql?вернусь trueк aсравнению aи ложно для aпротив b.
Nemo157

Ответы:

785

Я собираюсь привести цитату из документации по объекту здесь, потому что я думаю, что она имеет несколько хороших объяснений. Я рекомендую вам прочитать его, а также документацию для этих методов, так как они переопределены в других классах, таких как String .

Примечание: если вы хотите попробовать их на разных объектах, используйте что-то вроде этого:

class Object
  def all_equals(o)
    ops = [:==, :===, :eql?, :equal?]
    Hash[ops.map(&:to_s).zip(ops.map {|s| send(s, o) })]
  end
end

"a".all_equals "a" # => {"=="=>true, "==="=>true, "eql?"=>true, "equal?"=>false}

== - общее "равенство"

На уровне объекта ==возвращает true, только если objи otherявляются одним и тем же объектом. Как правило, этот метод переопределяется в классах-потомках, чтобы обеспечить специфичное для класса значение.

Это наиболее распространенное сравнение и, следовательно, самое фундаментальное место, где вы (как автор класса) можете решить, «равны» два объекта или нет.

=== - равенство случаев

Для класса Object, фактически, то же самое, что и вызов #==, но обычно переопределяется потомками, чтобы обеспечить содержательную семантику в операторах case.

Это невероятно полезно. Примеры вещей, которые имеют интересные ===реализации:

  • Спектр
  • Regex
  • Proc (в Ruby 1.9)

Таким образом, вы можете сделать такие вещи, как:

case some_object
when /a regex/
  # The regex matches
when 2..4
  # some_object is in the range 2..4
when lambda {|x| some_crazy_custom_predicate }
  # the lambda returned true
end

Посмотрите мой ответ здесь, чтобы получить хороший пример того, как case+ Regexможет сделать код намного чище. И, конечно, предоставляя собственную ===реализацию, вы можете получить пользовательскую caseсемантику.

eql?- Hashравенство

eql?Метод возвращает истину , если objи otherотносятся к одной и той же хэш - ключа. Это используется Hashдля проверки членов на равенство. Для объектов класса Object, eql?является синонимом ==. Подклассы обычно продолжают эту традицию, добавляя псевдонимы eql?к их переопределенному ==методу, но есть исключения. NumericТипы, например, выполняют преобразование типов через ==, но не через eql?, поэтому:

1 == 1.0     #=> true
1.eql? 1.0   #=> false

Таким образом, вы можете переопределить это для своих собственных нужд, или вы можете переопределить ==и использовать alias :eql? :==так, чтобы оба метода вели себя одинаково.

equal? - сравнение личности

В отличие от этого ==, equal?метод никогда не должен быть переопределен подклассами: он используется для определения идентичности объекта (т. a.equal?(b)Е. Если aтот же объект, что и b).

Это эффективное сравнение указателей.

jtbandes
источник
32
Как я понимаю из вашего ответа, строгость равна: равны? <EQL? <== <===. Обычно вы используете ==. Для некоторых свободных целей вы используете ===. Для строгой ситуации вы используете eql ?, а для полной идентичности вы используете равно ?.
Пила
21
Понятие строгости не соблюдается и даже не предлагается в документации, просто так получается, что Numericоно обрабатывается более строго, чем ==. Это действительно зависит от автора класса. ===редко используется вне caseутверждений.
jtbandes
4
== равенство с точки зрения большего / меньшего тоже. То есть, если вы включите Comparable, он будет определен в терминах <=> возврата 0. Вот почему 1 == 1.0 возвращает true.
Апейрос
5
@sawa Я обычно думаю о ===значении "спички" (примерно). Например, «совпадает ли регулярное выражение со строкой» или «соответствует ли диапазон (включает) число».
Кельвин
7
Интересный факт: официальные документы теперь ссылаются на этот ответ (см. Ruby-doc.org/core-2.1.5/… ).
Марк Амери
46

Мне нравится ответ jtbandes, но так как он довольно длинный, я добавлю свой компактный ответ:

==, ===, eql?,equal?
4 компараторов, то есть. 4 способа сравнить 2 объекта, в Ruby.
Поскольку в Ruby все компараторы (и большинство операторов) фактически являются вызовами методов, вы можете изменить, перезаписать и определить семантику этих методов сравнения самостоятельно. Однако важно понимать, когда внутренние языковые конструкции Ruby используют компаратор:

==(сравнение значений)
Ruby везде использует: == для сравнения значений двух объектов, например. Хэш-значения:

{a: 'z'}  ==  {a: 'Z'}    # => false
{a: 1}    ==  {a: 1.0}    # => true

===(сравнение случаев)
Ruby использует: === в случае / когда конструкции. Следующие фрагменты кода логически идентичны:

case foo
  when bar;  p 'do something'
end

if bar === foo
  p 'do something'
end

eql?(Сравнение хэш-ключей)
Ruby использует: eql? (в сочетании с методом hash) для сравнения Hash-ключей. В большинстве классов: eql? совпадает с: ==.
Знание о: eql? важно только тогда, когда вы хотите создать свои собственные специальные классы:

class Equ
  attr_accessor :val
  alias_method  :initialize, :val=
  def hash()           self.val % 2             end
  def eql?(other)      self.hash == other.hash  end
end

h = {Equ.new(3) => 3,  Equ.new(8) => 8,  Equ.new(15) => 15}    #3 entries, but 2 are :eql?
h.size            # => 2
h[Equ.new(27)]    # => 15

Примечание. Обычно используемый набор Ruby-класса также основан на сравнении хэш-ключей.

equal?(сравнение идентичности объекта)
Ruby использует: равно? проверить, идентичны ли два объекта. Этот метод (класса BasicObject) не должен быть перезаписан.

obj = obj2 = 'a'
obj.equal? obj2       # => true
obj.equal? obj.dup    # => false
Андреас Райо Книп
источник
30
Это хороший ответ, но он почти такой же длинный, как и у jtbandes. :)
чудо
2
@odigity, около 70%. Я мог бы придумать, на что потратить эти 30%.
Кэри Свовеланд,
Я думаю, что пример eql?очень вводит в заблуждение. eql?это сравнение на равенство, которое согласуется с тем, как вычисляется хеш, т.е. a.eql?(b)гарантирует это a.hash == b.hash. Он не просто сравнивает хеш-коды.
Андрей Таранцов
Действительно ли сравнение случаев эквивалентно bar === fooи нет foo === bar? Я надеюсь, что последнее правильно и важно, так как компилятор вызывает левую часть: === `'
Алексис Уилк
Насколько я знаю, это так bar === foo: Ruby использует значение case в левой части и переменную case в правой части. Это может быть связано с избеганием NPE (исключения нулевого указателя).
Андреас Райо Книп
34

Операторы равенства: == и! =

Оператор ==, также известный как равенство или двойное равенство, вернет true, если оба объекта равны, и false, если это не так.

"koan" == "koan" # Output: => true

Оператор! =, Также известный как неравенство, является противоположностью ==. Он вернет истину, если оба объекта не равны, и ложь, если они равны.

"koan" != "discursive thought" # Output: => true

Обратите внимание, что два массива с одинаковыми элементами в разном порядке не равны, прописные и строчные версии одной и той же буквы не равны и так далее.

При сравнении чисел разных типов (например, целых и чисел с плавающей точкой), если их числовые значения одинаковы, == вернет true.

2 == 2.0 # Output: => true

равны?

В отличие от оператора ==, который проверяет, равны ли оба операнда, метод равенства проверяет, ссылаются ли два операнда на один и тот же объект. Это самая строгая форма равенства в Ruby.

Пример: a = "zen" b = "zen"

a.object_id  # Output: => 20139460
b.object_id  # Output :=> 19972120

a.equal? b  # Output: => false

В приведенном выше примере у нас есть две строки с одинаковым значением. Однако это два разных объекта с разными идентификаторами объектов. Значит, равный? метод вернет false.

Попробуем еще раз, только на этот раз b будет ссылкой на a. Обратите внимание, что идентификатор объекта одинаков для обеих переменных, так как они указывают на один и тот же объект.

a = "zen"
b = a

a.object_id  # Output: => 18637360
b.object_id  # Output: => 18637360

a.equal? b  # Output: => true

EQL?

В классе Hash, eql? Метод используется для проверки ключей на равенство. Некоторый фон требуется, чтобы объяснить это. В общем контексте вычислений хеш-функция берет строку (или файл) любого размера и генерирует строку или целое число фиксированного размера, называемое хеш-кодом, обычно называемое только хеш-кодом. Некоторые часто используемые типы хеш-кодов: MD5, SHA-1 и CRC. Они используются в алгоритмах шифрования, индексации базы данных, проверке целостности файлов и т. Д. Некоторые языки программирования, такие как Ruby, предоставляют тип коллекции, называемый хэш-таблицей. Хеш-таблицы - это словарные коллекции, в которых данные хранятся парами, состоящими из уникальных ключей и соответствующих им значений. Под капотом эти ключи хранятся в виде хэш-кодов. Хеш-таблицы обычно называют просто хешами. Обратите внимание, как слово hash может относиться к хеш-коду или хеш-таблице.

Ruby предоставляет встроенный метод hash для генерации хеш-кодов. В приведенном ниже примере он принимает строку и возвращает хеш-код. Обратите внимание, что строки с одинаковым значением всегда имеют одинаковый хеш-код, даже если они являются разными объектами (с разными идентификаторами объектов).

"meditation".hash  # Output: => 1396080688894079547
"meditation".hash  # Output: => 1396080688894079547
"meditation".hash  # Output: => 1396080688894079547

Хеш-метод реализован в модуле Kernel, включенном в класс Object, который является корнем по умолчанию для всех объектов Ruby. Некоторые классы, такие как Symbol и Integer, используют реализацию по умолчанию, другие, такие как String и Hash, предоставляют свои собственные реализации.

Symbol.instance_method(:hash).owner  # Output: => Kernel
Integer.instance_method(:hash).owner # Output: => Kernel

String.instance_method(:hash).owner  # Output: => String
Hash.instance_method(:hash).owner  # Output: => Hash

В Ruby, когда мы храним что-то в хеше (коллекции), объект, предоставленный как ключ (например, строка или символ), преобразуется в и сохраняется как хеш-код. Позже, извлекая элемент из хеша (коллекции), мы предоставляем объект в качестве ключа, который преобразуется в хеш-код и сравнивается с существующими ключами. Если есть совпадение, возвращается значение соответствующего элемента. Сравнение сделано с помощью EQL? метод под капотом.

"zen".eql? "zen"    # Output: => true
# is the same as
"zen".hash == "zen".hash # Output: => true

В большинстве случаев, eql? Метод ведет себя аналогично методу ==. Однако есть несколько исключений. Например, eql? не выполняет неявное преобразование типов при сравнении целого числа с плавающей точкой.

2 == 2.0    # Output: => true
2.eql? 2.0    # Output: => false
2.hash == 2.0.hash  # Output: => false

Оператор равенства случая: ===

Многие встроенные в Ruby классы, такие как String, Range и Regexp, предоставляют свои собственные реализации оператора ===, также известного как case-равенства, тройного равенства или тройного равенства. Поскольку он реализован по-разному в каждом классе, он будет вести себя по-разному в зависимости от типа объекта, к которому он был вызван. Как правило, он возвращает true, если объект справа «принадлежит» или «является членом» объекта слева. Например, его можно использовать для проверки того, является ли объект экземпляром класса (или одним из его подклассов).

String === "zen"  # Output: => true
Range === (1..2)   # Output: => true
Array === [1,2,3]   # Output: => true
Integer === 2   # Output: => true

Тот же результат может быть достигнут с помощью других методов, которые, вероятно, лучше всего подходят для работы. Как правило, лучше писать код, который легко читать, будучи максимально явным, не жертвуя эффективностью и краткостью.

2.is_a? Integer   # Output: => true
2.kind_of? Integer  # Output: => true
2.instance_of? Integer # Output: => false

Обратите внимание, что последний пример вернул false, поскольку целые числа, такие как 2, являются экземплярами класса Fixnum, который является подклассом класса Integer. ===, is_a? и instance_of? методы возвращают true, если объект является экземпляром данного класса или любых подклассов. Метод instance_of является более строгим и возвращает true, только если объект является экземпляром этого точного класса, а не подклассом.

Is_a? и добрый_? методы реализованы в модуле Kernel, который смешивается с классом Object. Оба являются псевдонимами одного и того же метода. Давайте проверим:

Kernel.instance_method (: kind_of?) == Kernel.instance_method (: is_a?) # Выход: => true

Диапазон реализации ===

Когда оператор === вызывается для объекта диапазона, он возвращает true, если значение справа попадает в диапазон слева.

(1..4) === 3  # Output: => true
(1..4) === 2.345 # Output: => true
(1..4) === 6  # Output: => false

("a".."d") === "c" # Output: => true
("a".."d") === "e" # Output: => false

Помните, что оператор === вызывает метод === левого объекта. Итак, (1..4) === 3 эквивалентно (1..4). === 3. Другими словами, класс левого операнда определит, какая реализация метода === будет называется, так что позиции операндов не являются взаимозаменяемыми.

Regexp Реализация ===

Возвращает true, если строка справа соответствует регулярному выражению слева. / zen / === "тренируйся дзадзэн сегодня" # Вывод: => true # - то же самое, что и "тренируйся дзадзэн сегодня" = ~ / zen /

Неявное использование оператора === в операторах case / when

Этот оператор также используется под оператором case / when. Это его наиболее распространенное использование.

minutes = 15

case minutes
  when 10..20
    puts "match"
  else
    puts "no match"
end

# Output: match

В приведенном выше примере, если в Ruby неявно использовался оператор двойного равенства (==), диапазон 10..20 не будет считаться равным целому числу, например 15. Они совпадают, поскольку оператор тройного равенства (===) неявно используется во всех случаях / когда заявления. Код в приведенном выше примере эквивалентен:

if (10..20) === minutes
  puts "match"
else
  puts "no match"
end

Операторы сопоставления с образцом: = ~ и! ~

Операторы = ~ (equal-tilde) и! ~ (Bang-tilde) используются для сопоставления строк и символов с шаблонами регулярных выражений.

Реализация метода = ~ в классах String и Symbol предполагает регулярное выражение (экземпляр класса Regexp) в качестве аргумента.

"practice zazen" =~ /zen/   # Output: => 11
"practice zazen" =~ /discursive thought/ # Output: => nil

:zazen =~ /zen/    # Output: => 2
:zazen =~ /discursive thought/  # Output: => nil

Реализация в классе Regexp ожидает строку или символ в качестве аргумента.

/zen/ =~ "practice zazen"  # Output: => 11
/zen/ =~ "discursive thought" # Output: => nil

Во всех реализациях, когда строка или символ соответствуют шаблону Regexp, он возвращает целое число, которое является позицией (индексом) соответствия. Если совпадений нет, возвращается ноль. Помните, что в Ruby любое целочисленное значение является «правдивым», а nil - «ложным», поэтому оператор = ~ можно использовать в операторах if и троичных операторах.

puts "yes" if "zazen" =~ /zen/ # Output: => yes
"zazen" =~ /zen/?"yes":"no" # Output: => yes

Операторы сопоставления с образцом также полезны для написания более коротких операторов if. Пример:

if meditation_type == "zazen" || meditation_type == "shikantaza" || meditation_type == "kinhin"
  true
end
Can be rewritten as:
if meditation_type =~ /^(zazen|shikantaza|kinhin)$/
  true
end

Оператор! ~ Противоположен = ~, он возвращает истину, если совпадения нет, и ложь, если совпадение есть.

Более подробная информация доступна в этом блоге .

BrunoFacca
источник
6
Я считаю, что это лучший ответ, чем принятый в настоящее время, поскольку он дает хорошие примеры и не столь однозначен в отношении того, что означают различные виды равенства и почему они существуют / где они используются.
Qqwy
1
Очень подробный ответ, но на моем irb (ruby v 2.2.1) :zen === "zen"возвращается false
Mike R
@MikeR Спасибо, что сообщили мне об этом. Я исправил ответ.
BrunoFacca
Я думаю, что вы имеете в виду type_of? «Обратите внимание, что последний пример вернул false, потому что целые числа, такие как 2, являются экземплярами класса Fixnum, который является подклассом класса Integer. ===, is_a? И instance_of? (TYPE_OF?)"?
user1883793
1
Мне нравится этот ответ. Спасибо
Абдулла Фадхел
9

Ruby предоставляет несколько различных методов обработки равенства:

a.equal?(b) # object identity - a and b refer to the same object

a.eql?(b) # object equivalence - a and b have the same value

a == b # object equivalence - a and b have the same value with type conversion.

Продолжите чтение, нажав на ссылку ниже, это дало мне ясное обобщенное понимание.

https://www.relishapp.com/rspec/rspec-expectations/v/2-0/docs/matchers/equality-matchers

Надеюсь, что это помогает другим.

kalibbala
источник
8

=== # --- регистр равенства

== # --- общее равенство

оба работают одинаково, но "===" даже делают заявления случая

"test" == "test"  #=> true
"test" === "test" #=> true

здесь разница

String === "test"   #=> true
String == "test"  #=> false
Кишоре Мохан
источник
3
Они не работают подобным образом, хотя, как правило, это правда, когда a==bтогда a===b. Но a===bгораздо мощнее. ===не является симметричным, и a===bозначает совсем другую вещь b===a, не говоря уже о a==b.
mwfearnley
8

Я хотел бы расширить на ===оператора.

=== не является оператором равенства!

Не.

Давайте выясним это на самом деле.

Вы можете быть знакомы с ===оператором равенства в Javascript и PHP, но это просто не оператор равенства в Ruby и имеет принципиально другую семантику.

Так что же ===делать?

=== оператор сопоставления с шаблоном!

  • === соответствует регулярным выражениям
  • === проверяет членство в диапазоне
  • === проверяет наличие экземпляра класса
  • === вызывает лямбда-выражения
  • === иногда проверяет равенство, но в основном это не так

Так как же это безумие имеет смысл?

  • Enumerable#grepиспользует ===внутренне
  • case whenзаявления используют ===внутри
  • Интересный факт, rescueиспользует ===внутренне

Вот почему вы можете использовать регулярные выражения и классы и диапазоны и даже лямбда-выражения в case whenвыражении.

Некоторые примеры

case value
when /regexp/
  # value matches this regexp
when 4..10
  # value is in range
when MyClass
  # value is an instance of class
when ->(value) { ... }
  # lambda expression returns true
when a, b, c, d
  # value matches one of a through d with `===`
when *array
  # value matches an element in array with `===`
when x
  # values is equal to x unless x is one of the above
end

Все эти примеры работают pattern === valueкак с grepметодом , так и с ним .

arr = ['the', 'quick', 'brown', 'fox', 1, 1, 2, 3, 5, 8, 13]
arr.grep(/[qx]/)                                                                                                                            
# => ["quick", "fox"]
arr.grep(4..10)
# => [5, 8]
arr.grep(String)
# => ["the", "quick", "brown", "fox"]
arr.grep(1)
# => [1, 1]
akuhn
источник
-8

Я написал простой тест для всего вышеперечисленного.

def eq(a, b)
  puts "#{[a, '==',  b]} : #{a == b}"
  puts "#{[a, '===', b]} : #{a === b}"
  puts "#{[a, '.eql?', b]} : #{a.eql?(b)}"
  puts "#{[a, '.equal?', b]} : #{a.equal?(b)}"
end

eq("all", "all")
eq(:all, :all)
eq(Object.new, Object.new)
eq(3, 3)
eq(1, 1.0)
Том Фан
источник