Какая разница между равными?, eql?, === и ==?

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

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

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

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

Ответ 1

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

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

=== - равенство case

Для класса 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? возвращает 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) iff a - это тот же объект, что и b).

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

Ответ 2

Мне нравится ответ 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? (в сочетании с хешем метода) для сравнения Хэш-ключей. В большинстве классов: 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 использует: equal? чтобы проверить, идентичны ли два объекта. Этот метод (класса BasicObject) не должен быть перезаписан.

obj = obj2 = 'a'
obj.equal? obj2       # => true
obj.equal? obj.dup    # => false

Ответ 3

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

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

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

Оператор! =, неравенство AKA или bang-тильда, противоположно ==. Он вернет true, если оба объекта не равны и false, если они равны.

"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? метод используется для проверки ключей для равенства. Для объяснения этого требуется некоторый фон. В общем контексте вычислений хеш-функция принимает строку (или файл) любого размера и генерирует строку или целое число фиксированного размера, называемое hashcode, обычно называемое только хэшем. Некоторыми обычно используемыми типами хэш-кодов являются 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, когда мы храним что-то в хеше (коллекции), объект, предоставленный в виде ключа (например, строки или символа), преобразуется и сохраняется как хэш-код. Позже, при извлечении элемента из хэша (коллекции), мы предоставляем объект в качестве ключа, который преобразуется в хэш-код и сравнивается с существующими ключами. Если есть совпадение, возвращается значение соответствующего элемента. Сравнение производится с использованием уравнения метод под капотом.

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

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

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

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

Многие из встроенных классов Ruby, таких как String, Range и Regexp, предоставляют свои собственные реализации оператора ===, также известные как case-equal, triple equals или threequals. Поскольку он реализован по-разному в каждом классе, он будет вести себя по-разному в зависимости от типа объекта, на который он был вызван. Как правило, он возвращает 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? и kind_of? методы реализуются в модуле 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. Они совпадают, поскольку оператор тройного равенства (== =) неявно используется во всех операторах case/when. Код в приведенном выше примере эквивалентен:

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, он возвращает целое число, которое является позицией (индексом) совпадения. Если нет совпадения, он возвращает nil. Помните, что в 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

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

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

Ответ 4

=== # - равнозначное равенство

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

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

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

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

String === "test"   #=> true
String == "test"  #=> false

Ответ 5

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

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

Ответ 6

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

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

нет.

Позвольте получить эту точку на самом деле.

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

Ответ 7

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

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)