В чем разница между равными ?, eql ?, === и ==? - PullRequest
525 голосов
/ 23 августа 2011

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

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

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

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

Ответы [ 7 ]

756 голосов
/ 23 августа 2011

Я собираюсь здесь процитировать Документацию по объекту , потому что я думаю, что у нее есть несколько хороших объяснений.Я рекомендую вам прочитать его, а также документацию по этим методам, поскольку они переопределяются в других классах, например 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}

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

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

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

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

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

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

  • Range
  • 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?возвращает true, если 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).

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

45 голосов
/ 06 апреля 2015

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

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

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

{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 также основан на сравнении ключей Hash.

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

obj = obj2 = 'a'
obj.equal? obj2       # => true
obj.equal? obj.dup    # => false
29 голосов
/ 29 мая 2016

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

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

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

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

"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 слово хэш почти всегда относится к словароподобной коллекции.

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

"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, предоставляют свои собственные реализации оператора ===, также известного как равенство регистра, тройное равенство или тройное равенство.Поскольку он реализован по-разному в каждом классе, он будет вести себя по-разному в зависимости от типа объекта, к которому он был вызван.Как правило, он возвращает 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.Это его наиболее распространенное использование.

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

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

The = ~ (равный тильде) и!Операторы ~ (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 любое целочисленное значение является «правдивым», а ноль - «ложным», поэтому оператор = ~ можно использовать в операторах if и троичных операторах.полезно для написания более коротких операторов 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

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

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

8 голосов
/ 12 марта 2017

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

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

нет.

Давай проясним этот момент.

Возможно, вы знакомы с === как оператором равенства в 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]
8 голосов
/ 11 апреля 2015

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

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

8 голосов
/ 22 апреля 2014

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

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

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

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

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

String === "test"   #=> true
String == "test"  #=> false
0 голосов
/ 26 апреля 2014

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

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)
...