Яка різниця між рівними ?, eql ?, === і ==?


552

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

===за замовчуванням також називає ==які дзвінки equal?... гаразд, так що якщо всі ці три методи не будуть відмінені, то я гадаю ===, ==і equal?робити точно те саме?

Тепер приходить eql?. Що це робить (за замовчуванням)? Чи робить виклик хеш / id операнда?

Чому у Рубі стільки знаків рівності? Чи повинні вони відрізнятися семантикою?


Я тільки почав IRB і мав наступний результат , який суперечить вашим ... Всі ці 3 умови: "a" == "a", "a" === "a"і "a".eql? "a". Але це помилково: "a".equal? "a"(Mine is 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}

== - родова "рівність"

На рівні Об'єкт ==повертає істину лише в тому випадку, якщо objі otherє тим самим об'єктом. Зазвичай цей метод перекривається в класах нащадків, щоб забезпечити особливість класу.

Це найпоширеніше порівняння, і, отже, найбільш фундаментальне місце, де ви (як автор класу) можете вирішити, два об'єкти «рівні» чи ні.

=== - рівність випадку

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

Це неймовірно корисно. Приклади речей, які мають цікаві ===реалізації:

  • Дальність
  • Регекс
  • 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)iff a- це той самий об’єкт, що і b).

Це ефективно порівняння вказівників.


32
Як я розумію з вашої відповіді, суворість така: рівна? <екв? <== <===. Зазвичай ви використовуєте ==. Для деяких вільних цілей ви використовуєте ===. Для суворої ситуації ви використовуєте eql ?, а для повної ідентичності ви використовуєте рівну ?.
sawa

21
Поняття суворості не дотримується і навіть не пропонується в документації, просто так трапляється, що Numericповодитися з ним суворіше, ніж ==. Це справді залежить від автора класу. ===нечасто використовується поза caseтвердженнями.
jtbandes

4
== - рівність і в плані більшої / меншої. Тобто, якщо ви включите Порівняльний, він буде визначений через <=> повернення 0. Ось чому 1 == 1,0 повертає істину.
apeiros

5
@sawa Я зазвичай думаю про те ===, що означає "сірники" (приблизно). Як і в "Чи відповідає регулярний вираз рядок" або "Чи відповідає діапазон (включає) число".
Келвін

7
Веселий факт: офіційні документи тепер посилаються на цю відповідь (див. Ruby-doc.org/core-2.1.5/… ).
Марк Амері

46

Я люблю відповіді jtbandes, але оскільки вона досить довга, я додам свою компактну відповідь:

==, ===, eql?,equal?
4 компараторів, тобто. 4 способи порівняння 2 об’єктів, в Ruby.
Оскільки в Ruby всі компаратори (і більшість операторів) - це фактично виклики методів, ви можете самі змінювати, перезаписувати та визначати семантику цих методів порівняння. Однак важливо зрозуміти, коли внутрішні мовні конструкції Рубі використовують який компаратор:

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

{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, також покладається на порівняння клавіш Hash.

equal?(порівняння ідентичності об'єкта)
Ruby використовує: рівний? перевірити, чи два об'єкти однакові. Цей метод (класу BasicObject) не повинен бути перезаписаний.

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

30
Це хороша відповідь, але він майже майже такий, як jtbandes. :)
негідність

2
@подібність, приблизно 70%. Я міг би придумати багато речей, на які витратив ці 30%.
Cary Swoveland

Я думаю, що приклад eql?дуже хибний. eql?- це порівняння рівності, яке відповідає тому, як обчислюється хеш, тобто a.eql?(b)гарантує це a.hash == b.hash. Він не просто порівнює хеш-коди.
Андрій Таранцов

Чи справді порівняння справ рівнозначне bar === fooі ні foo === bar? Я сподіваюся, що остання правильна, і це важливо, оскільки компілятор закликає ліву частину: === `'
Alexis Wilke

Наскільки я знаю, це так bar === foo: Ruby використовує значення регістру зліва, а змінну регістру - на правому. Це може бути пов'язано з уникненням NPE (виключення з нульовими вказівниками).
Андреас Райо

33

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

Оператор ==, також відомий як рівність або подвійне рівне, поверне істину, якщо обидва об'єкти рівні та помилкові, якщо їх немає.

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

Оператор! =, Також відомий як нерівність, протилежний ==. Він повернеться істинним, якщо обидва об'єкти не рівні і хибні, якщо вони рівні.

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

Зауважте, що два масиви з однаковими елементами в різному порядку не рівні, великі та малі версії однієї і тієї ж літери не рівні і так далі.

Якщо порівнювати числа різних типів (наприклад, цілих чи плаваючих), якщо їх числове значення однакове, == повернеться істинним.

2 == 2.0 # Output: => true

рівний?

На відміну від оператора ==, який перевіряє, чи обидва операнди рівні, однаковий метод перевіряє, чи обидва операнди відносяться до одного об'єкта. Це найсуворіша форма рівності у Рубі.

Приклад: a = "zen" b = "zen"

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

a.equal? b  # Output: => false

У наведеному вище прикладі маємо дві струни з однаковим значенням. Однак вони є двома різними об'єктами, з різними ідентифікаторами об'єктів. Значить, рівне? метод поверне помилковий.

Спробуємо ще раз, лише цього разу b буде посиланням на a. Зауважте, що ідентифікатор об'єкта однаковий для обох змінних, оскільки вони вказують на один і той же об'єкт.

a = "zen"
b = a

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

a.equal? b  # Output: => true

екв?

У класі Hash, eql? метод використовується для тестування ключів на рівність. Для пояснення цього потрібно деякий досвід. У загальному контексті обчислень хеш-функція приймає рядок (або файл) будь-якого розміру і генерує рядок або ціле число фіксованого розміру, що називається хеш-кодом, зазвичай називають тільки хеш. Деякі типи хеш-кодів, які часто використовуються, - це MD5, SHA-1 та CRC. Вони використовуються в алгоритмах шифрування, індексації баз даних, перевірки цілісності файлів тощо. Деякі мови програмування, такі як 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? не виконує неявне перетворення типу при порівнянні цілого числа з плаваючою.

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

Зауважте, що останній приклад повернув помилкове значення, оскільки цілі числа, такі як 2, є екземплярами класу Fixnum, який є підкласом класу Integer. ===, is_a? і instance_of? методи повертають істину, якщо об'єкт є примірником даного класу або будь-якими підкласами. Метод instance_of є більш суворим і повертає true лише в тому випадку, якщо об'єкт є екземпляром цього точного класу, а не підкласом.

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

Kernel.instan_method (: kind_of?) == Kernel.instan_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 ===

Повертає значення true, якщо рядок праворуч відповідає регулярному виразу зліва. / zen / === "практика зацен сьогодні" # вихід: => true # те саме, що "практика зацен сьогодні" = ~ / zen /

Неявне використання оператора === для операторів case / when

Цей оператор також використовується під кришкою на випадок / коли. Це найпоширеніше його використання.

minutes = 15

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

# Output: match

У наведеному вище прикладі, якби Рубі неявно використав оператор подвійного рівного (==), діапазон 10..20 не вважався б рівним цілому числу, таким як 15. Вони відповідають тому, що потрійний оператор (===) є неявно використовується у всіх випадках / коли твердженнях. Код у наведеному вище прикладі еквівалентний:

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

Оператори відповідності шаблонів: = ~ і! ~

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

Реалізація методу = ~ у класах String and 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 будь-яке ціле значення є "truthy", а nil - "falsy", тому оператор = ~ може використовуватися в операторах, якщо оператори та потрійні оператори.

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

Оператори відповідності шаблонів також корисні для написання коротших, якщо висловлювань. Приклад:

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, якщо є збіг.

Більше інформації можна отримати у цій публікації в блозі .


6
Я вважаю це кращою відповіддю, ніж прийнята на даний момент відповідь, оскільки вона дає приємні приклади і менш амбітна щодо того, що означають різні види рівності та чому вони існують / де вони використовуються.
Qqwy

1
Дуже детальна відповідь, але на мій irb (ruby v 2.2.1) :zen === "zen"повертається неправдиво
Mike R

@MikeR Дякую, що повідомили мені. Я виправив відповідь.
BrunoFacca

Я думаю, ти маєш на увазі type_of? "Зауважте, що останній приклад повернув помилковий, оскільки цілі числа, такі як 2, - це екземпляри класу Fixnum, що є підкласом класу Integer. ===, is_a? Та instance_of? (TYPE_OF?)"?
користувач1883793

1
Я люблю цю відповідь. Спасибі
Абдулла Фадхель

9

Рубі розкриває кілька різних методів вирішення рівності:

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-expeasures/v/2-0/docs/matchers/equality-matchers

Сподіваюся, це допомагає іншим.


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]

-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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.