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

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

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

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

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

7 ответов


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

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

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}

== - generic "равенство"!--48-->

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

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

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

для объект класса, фактически то же самое, что и вызов #==, но обычно переопределяется потомками для обеспечения значимой семантики в операторах 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

посмотреть мой ответ здесь для a аккуратный пример того, как 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? - сравнение идентичности

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

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


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

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

== (сравнение стоимости)
Ruby использует: = = everywhere для сравнения значения из 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) сравнить хэш-ключи. В большинстве классов :Оку? совпадает с :==.
Знания о :Оку? важно только, когда вы хотите создать свои собственные специальные классы:

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

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

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

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

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

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

The != оператор, он же неравенство или bang-tilde, противоположен ==. Это возвратит true, если оба объекта не равны и false, если они равны.

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

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

при сравнении чисел разных типов (например, integer и float), если их числовое значение одинаково, = = вернет true.

2 == 2.0 # Output: => true

равной?

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

пример: дзен" си= "Дзен"

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?

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

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? способ ведѐт == способ. Однако, есть несколько исключений. Например, Оку? не выполняет неявное преобразование типов при сравнении целого числа с float.

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

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

многие встроенные классы Ruby, такие как String, Range и Regexp, предоставляют свои собственные реализации оператора===, также известного как case-equality, 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. The ===, is_a? и instance_of? методы возвращают значение true, если объект является экземпляром данного класса или его подклассов. Метод instance_of является более строгим и возвращает true только в том случае, если объект экземпляр именно этого класса, а не подкласса.

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

ядра.instance_method(:kind_of?) == Ядро.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 неявно использовал оператор double equal ( = = ), диапазон 10..20 не будет считаться равным целому числу, такому как 15. Они совпадают, потому что оператор тройного равенства (===) неявно используется во всех операторах case/when. Код в примере выше эквивалентен:

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

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

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

реализация метода =~ в классах String и Symbol ожидает регулярное выражение (an экземпляр класса 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

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

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

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


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

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

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

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

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

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

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

а.равны?( b) # Object identity - a и b относятся к одному и тому же объекту

а.Оку?(b) # эквивалентность объектов - a и b имеют одинаковое значение

a == b # эквивалентность объектов-a и b имеют одинаковое значение с преобразованием типа.

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

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

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


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

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

нет.

давайте действительно перейдем к этому вопросу.

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

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

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)