Jaka jest różnica między równym ?, eql ?, === i ==?

552

Próbuję zrozumieć różnicę między tymi czterema metodami. Wiem, że domyślnie ==wywołuje metodę, equal?która zwraca true, gdy oba operandy odnoszą się do dokładnie tego samego obiektu.

===domyślnie także wywołuje ==które połączenia equal?... dobrze, więc jeśli wszystkie te trzy metody nie zostaną zastąpione, to myślę ===, że ==i equal?robię dokładnie to samo?

Teraz nadchodzi eql?. Co to robi (domyślnie)? Czy wywołuje skrót / identyfikator operandu?

Dlaczego Ruby ma tyle znaków równości? Czy mają się różnić semantyką?

denniss
źródło
Właśnie rozpoczął IRB i miał następujący wynik, który zaprzecza twoje ... Wszystkie te 3 są prawdziwe: "a" == "a", "a" === "a"i "a".eql? "a". Ale to nieprawda: "a".equal? "a"(Mój jest rubinowy 1.9.2-p180)
PeterWong
7
@Peter: To dlatego, że ciągi zastępują wszystkie operatory równości. Próbując używając a = Object.new; b = Object.newwtedy wszystko ==, ===, .equal?, .eql?powróci truedo avs afalse dla avs b.
Nemo157,

Odpowiedzi:

785

Zamierzam zacytować tutaj dokumentację Object , ponieważ myślę, że zawiera ona kilka świetnych wyjaśnień. Zachęcam do przeczytania go, a także dokumentacji tych metod, ponieważ są one zastępowane w innych klasach, takich jak String .

Uwaga dodatkowa: jeśli chcesz je wypróbować na różnych obiektach, użyj czegoś takiego:

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}

== - ogólna „równość”

Na poziomie obiektu, ==zwraca wartość true tylko wtedy, gdy obji otherto ten sam obiekt. Zazwyczaj metoda ta jest zastępowana w klasach potomnych, aby nadać znaczenie specyficzne dla klasy.

Jest to najczęstsze porównanie, a zatem najbardziej fundamentalne miejsce, w którym (jako autor klasy) decydujesz, czy dwa obiekty są „równe”, czy nie.

=== - równość wielkości liter

W przypadku klasy Object, efektywnie to samo co wywołanie #==, ale zwykle zastępowane przez potomków, aby zapewnić znaczącą semantykę w instrukcjach case.

Jest to niezwykle przydatne. Przykłady rzeczy, które mają ciekawe ===implementacje:

  • Zasięg
  • Regex
  • Proc (w Ruby 1.9)

Możesz więc robić takie rzeczy jak:

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

Zobacz moją odpowiedź tutaj, aby znaleźć dobry przykład tego, jak case+ Regexmoże uczynić kod o wiele czystszym. Oczywiście, udostępniając własną ===implementację, można uzyskać niestandardową casesemantykę.

eql?- Hashrówność

eql?Metoda zwraca true jeśli obji otherodnoszą się do tego samego klawisza skrótu. Służy to Hashdo testowania członków pod kątem równości. Dla obiektów klasy Object, eql?jest synonimem ==. Podklasy zwykle kontynuują tę tradycję, aliasując eql?do ich przesłoniętej ==metody, ale są wyjątki. Numerictypy, na przykład, wykonują konwersję typów w poprzek ==, ale nie w poprzek eql?, więc:

1 == 1.0     #=> true
1.eql? 1.0   #=> false

Możesz więc zastąpić to dla własnych celów lub możesz zastąpić ==i użyć, alias :eql? :==aby obie metody zachowywały się w ten sam sposób.

equal? - porównanie tożsamości

W przeciwieństwie do ==tej equal?metody nigdy nie należy zastępować jej podklasami: służy ona do określania tożsamości obiektu (tzn. a.equal?(b)Iff ajest tym samym obiektem co b).

Jest to efektywne porównanie wskaźników.

jtbandes
źródło
32
Jak rozumiem z twojej odpowiedzi, surowość jest: równa? <eql? <== <===. Zwykle używasz ==. W niektórych luźnych celach używasz ===. W ściśle określonych sytuacjach używasz eql ?, a dla pełnej tożsamości używasz równego ?.
sawa
21
Pojęcie ścisłości nie jest egzekwowane ani nawet sugerowane w dokumentacji, po prostu dzieje się tak, że Numerictraktuje się go bardziej rygorystycznie niż ==. To naprawdę zależy od autora klasy. ===jest rzadko używany poza caseinstrukcjami.
jtbandes
4
== to równość również w kategoriach większych / mniejszych. Oznacza to, że jeśli uwzględnisz wartość Porównywalną, zostanie zdefiniowana pod kątem zwracania wartości <=> 0. Dlatego 1 == 1.0 zwraca wartość true.
apeiros
5
@sawa Zwykle uważam, że ===oznacza „mecze” (z grubsza). Jak w „czy wyrażenie regularne pasuje do ciągu” lub „czy zakres odpowiada (obejmuje) liczbę”.
Kelvin
7
Ciekawostka: oficjalne dokumenty zawierają teraz link do tej odpowiedzi (patrz ruby-doc.org/core-2.1.5/… ).
Mark Amery
46

Uwielbiam odpowiedź jtbandes, ale ponieważ jest dość długa, dodam własną kompaktową odpowiedź:

==, ===, eql?,equal?
Są 4 komparatory, tj. 4 sposoby porównywania 2 obiektów w Ruby.
Ponieważ w Ruby wszystkie komparatory (i większość operatorów) są w rzeczywistości wywołaniami metod, możesz sam zmienić, zastąpić i zdefiniować semantykę tych metod porównywania. Jednak ważne jest, aby zrozumieć, kiedy konstrukcje języka wewnętrznego Ruby używają tego komparatora:

==(porównanie wartości)
Ruby używa: == wszędzie, aby porównać wartości 2 obiektów, np. Wartości skrótu:

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

===(porównanie wielkości liter)
Ruby używa: === w przypadku / kiedy konstruuje. Następujące fragmenty kodu są logicznie identyczne:

case foo
  when bar;  p 'do something'
end

if bar === foo
  p 'do something'
end

eql?(Porównanie klucza skrótu)
Używa Ruby: eql? (w połączeniu z skrótem metody), aby porównać klucze skrótu. W większości klas: eql? jest identyczny z: ==.
Wiedza na temat: eql? jest ważne tylko wtedy, gdy chcesz stworzyć własne klasy specjalne:

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

Uwaga: Powszechnie używany zestaw klasy Ruby opiera się również na porównaniu klucza skrótu.

equal?(porównanie tożsamości obiektu)
Używa Ruby: równy? aby sprawdzić, czy dwa obiekty są identyczne. Ta metoda (klasy BasicObject) nie powinna zostać nadpisana.

obj = obj2 = 'a'
obj.equal? obj2       # => true
obj.equal? obj.dup    # => false
Andreas Rayo Kniep
źródło
30
To dobra odpowiedź, ale prawie tak długo, jak jtbandes. :)
odigity
2
@odigity, około 70% tak długo. Mógłbym wymyślić wiele rzeczy, na które można wydać te 30%.
Cary Swoveland
Myślę, że przykład eql?jest bardzo mylący. eql?to porównanie równości, które jest spójne ze sposobem obliczania wartości skrótu, tzn. a.eql?(b)gwarantuje to a.hash == b.hash. To nie nie po prostu porównać kody hash.
Andrey Tarantsov
Czy porównanie przypadków jest naprawdę równoważne bar === fooczy nie foo === bar? Mam nadzieję, że to drugie jest poprawne i jest ważne, ponieważ kompilator wywołuje lewą stronę: === ``
Alexis Wilke
O ile mi wiadomo, bar === fooRuby używa wartości wielkości liter po lewej stronie i zmiennej wielkości liter po prawej stronie. Może to mieć związek z unikaniem NPE (wyjątki zerowego wskaźnika).
Andreas Rayo Kniep
33

Operatory równości: == i! =

Operator ==, znany również jako równość lub podwójna równość, zwróci true, jeśli oba obiekty są równe, a false, jeśli nie są.

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

Operator! =, Znany również jako nierówność, jest przeciwieństwem ==. Zwróci wartość true, jeśli oba obiekty nie są równe, i false, jeśli są równe.

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

Zauważ, że dwie tablice z tymi samymi elementami w innej kolejności nie są równe, wielkie i małe wersje tej samej litery nie są równe i tak dalej.

Podczas porównywania liczb różnych typów (np. Liczba całkowita i liczba zmiennoprzecinkowa), jeśli ich wartość liczbowa jest taka sama, == zwróci wartość true.

2 == 2.0 # Output: => true

równy?

W przeciwieństwie do operatora ==, który sprawdza, czy oba argumenty są równe, metoda równości sprawdza, czy dwa argumenty odnoszą się do tego samego obiektu. Jest to najostrzejsza forma równości w Rubim.

Przykład: a = „zen” b = „zen”

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

a.equal? b  # Output: => false

W powyższym przykładzie mamy dwa ciągi o tej samej wartości. Są to jednak dwa odrębne obiekty o różnych identyfikatorach obiektów. Stąd równy? metoda zwróci false.

Spróbujmy jeszcze raz, tylko tym razem b będzie odniesieniem do a. Zauważ, że identyfikator obiektu jest taki sam dla obu zmiennych, ponieważ wskazują one na ten sam obiekt.

a = "zen"
b = a

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

a.equal? b  # Output: => true

eql?

W klasie Hash eql? metoda służy do testowania kluczy pod kątem równości. Aby to wyjaśnić, wymagane jest pewne doświadczenie. W ogólnym kontekście obliczeń funkcja skrótu pobiera ciąg (lub plik) o dowolnym rozmiarze i generuje ciąg lub liczbę całkowitą o stałym rozmiarze, zwaną hashcode, zwaną zwykle hashem. Niektóre powszechnie używane typy skrótów to MD5, SHA-1 i CRC. Są one używane w algorytmach szyfrowania, indeksowaniu baz danych, sprawdzaniu integralności plików itp. Niektóre języki programowania, takie jak Ruby, zapewniają typ kolekcji zwany tablicą skrótów. Tabele skrótów są kolekcjami przypominającymi słownik, które przechowują dane w parach, składające się z unikalnych kluczy i odpowiadających im wartości. Pod maską te klucze są przechowywane jako kody skrótu. Tabele skrótów są powszechnie nazywane skrótami. Zauważ, jak słowo hashcan może odnosić się do hashcode lub do tablicy hash.

Ruby zapewnia wbudowaną metodę zwaną hash do generowania skrótów. W poniższym przykładzie pobiera ciąg znaków i zwraca kod skrótu. Zauważ, że ciągi o tej samej wartości zawsze mają ten sam kod skrótu, nawet jeśli są odrębnymi obiektami (o różnych identyfikatorach obiektów).

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

Metoda skrótu jest zaimplementowana w module jądra, zawartym w klasie Object, która jest domyślnym katalogiem głównym wszystkich obiektów Ruby. Niektóre klasy, takie jak Symbol i Integer, używają domyślnej implementacji, inne jak String i Hash zapewniają własne implementacje.

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

W Ruby, kiedy przechowujemy coś w haszu (kolekcji), obiekt podany jako klucz (np. Łańcuch lub symbol) jest konwertowany i zapisywany jako kod skrótu. Później, podczas pobierania elementu z skrótu (kolekcji), udostępniamy obiekt jako klucz, który jest konwertowany na kod skrótu i ​​porównywany z istniejącymi kluczami. Jeśli istnieje dopasowanie, zwracana jest wartość odpowiedniego elementu. Porównanie odbywa się za pomocą eql? metoda pod maską.

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

W większości przypadków eql? Metoda zachowuje się podobnie do metody ==. Jest jednak kilka wyjątków. Na przykład eql? nie wykonuje niejawnej konwersji typu podczas porównywania liczby całkowitej z liczbą zmiennoprzecinkową.

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

Operator równości wielkości: ===

Wiele wbudowanych klas Ruby, takich jak String, Range i Regexp, zapewnia własne implementacje operatora ===, znanego również jako równość wielkości liter, potrójne równe lub trzykwale. Ponieważ jest zaimplementowany inaczej w każdej klasie, będzie zachowywać się inaczej w zależności od typu obiektu, do którego został wywołany. Zasadniczo zwraca wartość true, jeśli obiekt po prawej „należy do” lub „jest członkiem” obiektu po lewej stronie. Na przykład można go użyć do przetestowania, czy obiekt jest instancją klasy (lub jednej z jej podklas).

String === "zen"  # Output: => true
Range === (1..2)   # Output: => true
Array === [1,2,3]   # Output: => true
Integer === 2   # Output: => true

Ten sam wynik można osiągnąć za pomocą innych metod, które prawdopodobnie najlepiej nadają się do pracy. Zwykle lepiej jest napisać kod, który jest łatwy do odczytania, tak wyraźny, jak to możliwe, bez poświęcania wydajności i zwięzłości.

2.is_a? Integer   # Output: => true
2.kind_of? Integer  # Output: => true
2.instance_of? Integer # Output: => false

Zwróć uwagę, że ostatni przykład zwrócił wartość false, ponieważ liczby całkowite, takie jak 2, są instancjami klasy Fixnum, która jest podklasą klasy Integer. ===, is_a? i instance_of? metody zwracają wartość true, jeśli obiekt jest instancją danej klasy lub dowolnej podklasy. Metoda instance_of jest bardziej rygorystyczna i zwraca true tylko wtedy, gdy obiekt jest instancją tej właśnie klasy, a nie podklasą.

Czy to jest? i rodzaj? metody są zaimplementowane w module jądra, który jest mieszany przez klasę Object. Oba są pseudonimami tej samej metody. Sprawdźmy:

Kernel.instance_method (: kind_of?) == Kernel.instance_method (: is_a?) # Output: => true

Zakres realizacji ===

Gdy operator === jest wywoływany na obiekcie zakresu, zwraca wartość true, jeśli wartość po prawej stronie mieści się w zakresie po lewej stronie.

(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

Pamiętaj, że operator === wywołuje metodę === obiektu po lewej stronie. Więc (1..4) === 3 jest równoważne z (1..4). === 3. Innymi słowy, klasa operandu po lewej stronie określi, która implementacja metody === będzie wywołane, więc pozycje operandu nie są zamienne.

Implementacja Regexp ===

Zwraca true, jeśli ciąg po prawej stronie pasuje do wyrażenia regularnego po lewej. / zen / === „ćwiczenie zazen dzisiaj” # Wyjście: => true # to to samo co „ćwiczenie zazen dzisiaj” = ~ / zen /

Niejawne użycie operatora === w instrukcjach case / when

Ten operator jest również używany pod maską na instrukcjach dotyczących przypadków / kiedy. To jest jego najczęstsze zastosowanie.

minutes = 15

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

# Output: match

W powyższym przykładzie, gdyby Ruby domyślnie zastosowała operator podwójnej równości (==), zakres 10..20 nie byłby uważany za równy liczbie całkowitej, takiej jak 15. Pasują, ponieważ potrójny operator równości (===) to domyślnie używane we wszystkich instrukcjach case / when. Kod w powyższym przykładzie jest równoważny z:

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

Operatory dopasowywania wzorców: = ~ i! ~

Operatory = ~ (równa tylda) i! ~ (Bang-tylda) służą do dopasowania ciągów i symboli do wzorców wyrażeń regularnych.

Implementacja metody = ~ w klasach String i Symbol oczekuje wyrażenia regularnego (instancji klasy Regexp) jako argumentu.

"practice zazen" =~ /zen/   # Output: => 11
"practice zazen" =~ /discursive thought/ # Output: => nil

:zazen =~ /zen/    # Output: => 2
:zazen =~ /discursive thought/  # Output: => nil

Implementacja w klasie Regexp oczekuje ciągu lub symbolu jako argumentu.

/zen/ =~ "practice zazen"  # Output: => 11
/zen/ =~ "discursive thought" # Output: => nil

We wszystkich implementacjach, gdy ciąg lub symbol pasuje do wzorca Regexp, zwraca liczbę całkowitą, która jest pozycją (indeksem) dopasowania. Jeśli nie ma dopasowania, zwraca zero. Pamiętaj, że w Rubim każda wartość całkowita jest „prawda”, a zero to „fałsz”, więc operator = ~ może być użyty w instrukcjach if i operatorach trójskładnikowych.

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

Operatory dopasowywania wzorców są również przydatne do pisania krótszych instrukcji if. Przykład:

if meditation_type == "zazen" || meditation_type == "shikantaza" || meditation_type == "kinhin"
  true
end
Can be rewritten as:
if meditation_type =~ /^(zazen|shikantaza|kinhin)$/
  true
end

Operator! ~ Jest przeciwieństwem = ~, zwraca true, gdy nie ma dopasowania, i false, jeśli istnieje dopasowanie.

Więcej informacji jest dostępnych w tym poście na blogu .

BrunoFacca
źródło
6
Uważam, że jest to lepsza odpowiedź niż obecnie akceptowana, ponieważ zawiera ładne przykłady i jest mniej dwuznaczna, co oznaczają różne rodzaje równości i dlaczego istnieją / gdzie są stosowane.
Qqwy
1
Bardzo szczegółowa odpowiedź, ale na moim irb (ruby v 2.2.1) :zen === "zen"zwraca false
Mike R
@MikeR Dziękujemy za poinformowanie mnie. Poprawiłem odpowiedź.
BrunoFacca
Myślę, że masz na myśli type_of? „Zauważ, że ostatni przykład zwrócił wartość false, ponieważ liczby całkowite, takie jak 2, są instancjami klasy Fixnum, która jest podklasą klasy Integer. ===, is_a? I instance_of? (TYPE_OF?)”?
user1883793
1
Uwielbiam tę odpowiedź. Dzięki
Abdullah Fadhel,
9

Ruby przedstawia kilka różnych metod postępowania z równością:

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.

Kontynuuj czytanie, klikając poniższy link, co dało mi jasne podsumowanie.

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

Mam nadzieję, że pomaga innym.

kalibbala
źródło
8

=== # --- równość wielkości liter

== # --- ogólna równość

oba działają podobnie, ale „===” nawet wykonuje instrukcje case

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

tutaj różnica

String === "test"   #=> true
String == "test"  #=> false
Kishore Mohan
źródło
3
Oni nie działają podobnie, mimo że wydaje się być prawdą, że gdy a==bpotem a===b. Ale a===bjest znacznie potężniejszy. ===nie jest symetryczny i a===boznacza coś zupełnie innego niż b===a, nie mówiąc już o tym a==b.
mwfearnley
8

Chciałbym rozwinąć ===operatora.

=== nie jest operatorem równości!

Nie.

Przejdźmy do tego punktu naprawdę.

Być może znasz się ===jako operator równości w JavaScript i PHP, ale to po prostu nie jest operator równości w Ruby i ma zasadniczo inną semantykę.

Co więc robi ===?

=== jest operatorem dopasowywania wzorców!

  • === dopasowuje wyrażenia regularne
  • === sprawdza członkostwo w zakresie
  • === sprawdza, czy jest instancją klasy
  • === wywołuje wyrażenia lambda
  • === czasami sprawdza równość, ale przeważnie nie

Więc jak to szaleństwo ma sens?

  • Enumerable#grepwykorzystuje ===wewnętrznie
  • case when używać instrukcji === wewnętrznie
  • Ciekawostka, rescuewykorzystuje ===wewnętrznie

Dlatego możesz używać wyrażeń regularnych oraz klas i zakresów, a nawet wyrażeń lambda w case when .

Kilka przykładów

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

Wszystkie te przykłady pattern === valuerównież działają z grepmetodami.

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]
akuhn
źródło
-8

Napisałem prosty test na wszystkie powyższe.

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)
Tom Phan
źródło