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

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

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

Теперь идет eql?. Что это делает (по умолчанию)? Обращается ли он к хешу / идентификатору операнда?

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


person denniss    schedule 23.08.2011    source источник
comment
Я только что запустил irb и получил следующий результат, который противоречит вашему ... Все эти 3 верны: "a" == "a", "a" === "a" и "a".eql? "a". Но это неверно: "a".equal? "a" (у меня рубин 1.9.2-p180)   -  person PeterWong    schedule 23.08.2011
comment
@Peter: Это потому, что строки переопределяют все операторы равенства. Попытка использовать a = Object.new; b = Object.new, тогда все ==, ===, .equal?, .eql? вернут true для a vs a и false для a vs b.   -  person Nemo157    schedule 23.08.2011


Ответы (8)


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

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

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}

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

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

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

=== - равенство падежей

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

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

  • Диапазон
  • Регулярное выражение
  • 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

См. Аккуратный пример мой ответ здесь о том, как _10 _ + _ 11_ может сделать код намного чище. И, конечно же, предоставляя свою собственную === реализацию, вы можете получить пользовательскую 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).

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

person jtbandes    schedule 23.08.2011
comment
Как я понял из вашего ответа, строгость такая: равная? ‹Eql? ‹==‹ ===. Обычно вы используете ==. Для некоторых целей вы используете ===. В строгой ситуации вы используете eql ?, а для полной идентичности вы используете equal ?. - person sawa; 12.10.2012
comment
Понятие строгости не применяется и даже не предлагается в документации, так уж получилось, что Numeric обрабатывает его более строго, чем ==. Это действительно зависит от автора класса. === редко используется вне операторов case. - person jtbandes; 13.10.2012
comment
== - это равенство с точки зрения большего / меньшего. То есть, если вы включите Comparable, он будет определен в терминах ‹=›, возвращающих 0. Вот почему 1 == 1.0 возвращает true. - person apeiros; 04.11.2012
comment
@sawa Я обычно думаю о === как о значении совпадений (примерно). Например, соответствует ли регулярное выражение строке или диапазон соответствует (включает) число. - person Kelvin; 18.05.2013
comment
Отличный ответ. Один вопрос, который он оставил для меня без ответа, заключается в том, наследуются ли какие-либо из этих равенств друг от друга. Если я переопределю ==, изменится ли это поведение ===? И так далее. Немного поиграв, показывает, что === принимает поведение ==, но == и eql? не влияют друг на друга. - person ChrisPhoenix; 09.09.2013
comment
FWIW, проверка поведения с a = [1, 2, 3]; b = [1, 2, 3] мне помогли. - person Bob Stine; 21.03.2014
comment
Другой класс, который переопределяет ===, - это Class; он работает аналогично is_a?. Вот почему вы можете: case obj; when String; :string; when Array; :array end - person Kelvin; 18.07.2014
comment
eql? следует называть равенством хэша, потому что он используется при проверке совпадения хеш-ключей. Вот почему {1=>true, 1.0=>true}.size == 2 и [1, 1.0].size == 2. Из документации по объекту: eql? метод возвращает истину, если obj и другие ссылаются на один и тот же хеш-ключ. - person Daniel; 26.08.2014
comment
Спасибо @Daniel, я обновил свой ответ, чтобы включить те изменения, которые были внесены в документы с тех пор, как я написал его 3 года назад :) - person jtbandes; 26.08.2014
comment
Интересный факт: официальные документы теперь ссылаются на этот ответ (см. ruby-doc.org/core-2.1.5/). - person Mark Amery; 04.12.2014
comment
Вот еще один способ запомнить это: чем длиннее оператор, тем строже он становится; кроме ===. wellho.net/mouth/985_Equality-in-Ruby -eql-and-equal-.html - person Iggy; 18.09.2016
comment
Обратите внимание, что вы должны переопределить eql? в ваших собственных подклассах для работы uniq и т. д. В этом нет необходимости - в документации для object.c говорится, что метод ‹code› eql? ‹/Code› возвращает ‹code› true ‹/code›, если + obj + и + other + относятся к одному и тому же хэш-ключу. - но, очевидно, базовый класс на самом деле этого не делает, а просто сравнивает указатели на объекты - см. github.com/ruby/ruby/edit/trunk/object.c#L205 - person AlexChaffee; 10.03.2018

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

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

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

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

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

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
person Andreas Rayo Kniep    schedule 06.04.2015
comment
Это хороший ответ, но он почти такой же, как у jtbandes. :) - person odigity; 04.10.2015
comment
@odigity, примерно на 70% дольше. Я мог придумать, на что потратить эти 30%. - person Cary Swoveland; 18.10.2018
comment
Я думаю, что пример eql? вводит в заблуждение. eql? - это сравнение на равенство, которое согласуется с тем, как вычисляется хэш, т.е. a.eql?(b) гарантирует, что a.hash == b.hash. Он не просто сравнивает хэш-коды. - person Andrey Tarantsov; 02.07.2019
comment
Действительно ли сравнение случаев эквивалентно bar === foo, а не foo === bar? Я надеюсь, что последнее верно, и это важно, поскольку компилятор вызывает левую часть: === `' - person Alexis Wilke; 11.10.2019
comment
Насколько мне известно, это bar === foo: Ruby использует значение case слева и переменную case справа. Возможно, это связано с тем, чтобы избежать NPE (исключений нулевого указателя). - person Andreas Rayo Kniep; 22.10.2019
comment
Будет использован === метод объекта, использованного в when. Таким образом, это будет === метод when, который будет определять семантику для соответствия. Например. Module#=== выполняет is_a? нравится, а Regexp#=== - match. - person Paul van Leeuwen; 21.02.2020

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

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

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

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

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

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

При сравнении чисел разных типов (например, integer и float), если их числовое значение совпадает, == вернет 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 слово «хеш» почти всегда относится к коллекции, подобной словарю.

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

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

Метод хеширования реализован в модуле ядра, включенном в класс 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, предоставляют свои собственные реализации оператора ===, также известного как равенство регистра, тройное равенство или тройное равенство. Поскольку он реализован по-разному в каждом классе, он будет вести себя по-разному в зависимости от типа объекта, к которому он был вызван. Как правило, он возвращает истину, если объект справа «принадлежит» или «является членом» объекта слева. Например, его можно использовать для проверки того, является ли объект экземпляром класса (или одним из его подклассов).

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? методы возвращают истину, если объект является экземпляром данного класса или любых подклассов. Метод instance_of более строгий и возвращает true только в том случае, если объект является экземпляром этого точного класса, а не подклассом.

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

Kernel.instance_method (: kind_of?) == Kernel.instance_method (: is_a?) # Вывод: => 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 Реализация ===

Возвращает истину, если строка справа соответствует регулярному выражению слева. / 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. Они совпадают, потому что оператор тройного равенства (===) равен неявно используется во всех операторах case / when. Код в приведенном выше примере эквивалентен:

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

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

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

Реализация метода = ~ в классах 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

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

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

person BrunoF    schedule 29.05.2016
comment
Я считаю, что это лучший ответ, чем принятый в настоящее время ответ, поскольку он предоставляет хорошие примеры и менее двусмыслен в отношении того, что означают различные виды равенства и почему они существуют / где они используются. - person Qqwy; 24.06.2016
comment
Очень подробный ответ, но на моем irb (ruby v 2.2.1) :zen === "zen" возвращает false - person Mike R; 02.07.2016
comment
@MikeR Спасибо, что сообщили мне. Я поправил ответ. - person BrunoF; 02.07.2016
comment
Я думаю, вы имеете в виду type_of? Обратите внимание, что последний пример вернул false, потому что целые числа, такие как 2, являются экземплярами класса Fixnum, который является подклассом класса Integer. Знак ===, is_a? и instance_of? (ТИП?) ? - person user1883793; 27.10.2016
comment
Мне нравится этот ответ. Спасибо - person Abdullah Fadhel; 19.09.2017
comment
Утверждение, что a.eql?(b) то же самое, что a.hash == b.hash, неверно. a.eql?(b) подразумевает a.hash == b.hash, но обратное неверно: хэш-коды могут совпадать, даже если объекты не eql?. - person Andrey Tarantsov; 02.07.2019
comment
Это правда, но насколько вероятно, что это произойдет в реальном мире? - person BrunoF; 02.07.2019

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

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

Нет.

Давайте по-настоящему разъясним эту мысль.

Возможно, вы знакомы с === как с оператором равенства в 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]
person akuhn    schedule 12.03.2017

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

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

person kalibbala    schedule 11.04.2015

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

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

оба работают одинаково, но "===" даже делают операторы case

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

вот в чем разница

String === "test"   #=> true
String == "test"  #=> false
person Kishore Mohan    schedule 22.04.2014
comment
Они не работают аналогичным образом, хотя обычно бывает так, что когда a==b, то a===b. Но a===b намного мощнее. === не является симметричным, и a===b означает совсем другое, чем b===a, не говоря уже о a==b. - person mwfearnley; 16.09.2015

  1. .eql? - Этот оператор возвращает истину, если получатель и аргумент имеют один и тот же тип и равные значения.

например - 10.eql? (10.0) ложно.

  1. === - он проверит равенство в операторе case.

например - (1 ... 10) === 1 верно

  1. == - этот оператор проверяет, равны ли два заданных операнда. Если равно, возвращается ИСТИНА, в противном случае возвращается ЛОЖЬ.

например - (1 ... 10) == 1 ложно

для получения дополнительных примеров нажмите здесь

person pratik    schedule 02.06.2021

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

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)
person Tom Phan    schedule 25.04.2014