Qual è la differenza tra equal ?, eql ?, === e ==?


552

Sto cercando di capire la differenza tra questi quattro metodi. So per impostazione predefinita che ==chiama il metodo equal?che ritorna vero quando entrambi gli operandi si riferiscono esattamente allo stesso oggetto.

===Per impostazione predefinita, anche le chiamate ==che le chiamate equal?... Va bene, quindi, se tutti e tre questi metodi non sono sovrascritti, allora immagino ===, ==e equal?fanno esattamente la stessa cosa?

Ora arriva eql?. Cosa fa (di default)? Fa una chiamata all'hash / id dell'operando?

Perché Ruby ha così tanti segni di uguaglianza? Dovrebbero differire in semantica?


Ho appena iniziato un IRB e ha avuto il seguente risultato che contraddice il vostro ... Tutti questi 3 sono vere: "a" == "a", "a" === "a"e "a".eql? "a". Ma questo è falso: "a".equal? "a"(il mio è rubino 1.9.2-p180)
PeterWong

7
@Peter: Questo perché le stringhe hanno la precedenza su tutti gli operatori di uguaglianza. Cercando utilizzando a = Object.new; b = Object.newpoi tutti ==, ===, .equal?, .eql?tornerà trueper la avs ae falso per avs b.
Nemo157,

Risposte:


785

Citerò pesantemente la documentazione dell'Oggetto qui, perché penso che abbia delle grandi spiegazioni. Ti incoraggio a leggerlo, e anche la documentazione per questi metodi poiché sono sovrascritti in altre classi, come String .

Nota a margine: se vuoi provarli tu stesso su oggetti diversi, usa qualcosa del genere:

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}

== - "uguaglianza" generica

A livello di oggetto, ==restituisce vero solo se obje othersono lo stesso oggetto. In genere, questo metodo viene sovrascritto nelle classi discendenti per fornire un significato specifico della classe.

Questo è il confronto più comune, e quindi il luogo più fondamentale in cui tu (come autore di una classe) puoi decidere se due oggetti sono "uguali" o meno.

=== - uguaglianza dei casi

Per Object class, effettivamente lo stesso di una chiamata #==, ma in genere sovrascritto dai discendenti per fornire una semantica significativa nelle dichiarazioni dei casi.

Questo è incredibilmente utile. Esempi di cose che hanno ===implementazioni interessanti :

  • Gamma
  • regex
  • Proc (in Ruby 1.9)

Quindi puoi fare cose come:

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

Vedi la mia risposta qui per un chiaro esempio di come case+ Regexpuò rendere il codice molto più pulito. E, naturalmente, fornendo la tua ===implementazione, puoi ottenere una casesemantica personalizzata .

eql?- Hashuguaglianza

Il eql?metodo restituisce true se obje fa otherriferimento alla stessa chiave hash. Viene utilizzato da Hashper verificare la parità dei membri. Per oggetti di classe Object, eql?è sinonimo di ==. Le sottoclassi normalmente continuano questa tradizione aliasando il eql?loro ==metodo ignorato , ma ci sono eccezioni. Numerici tipi, ad esempio, eseguono la conversione dei tipi attraverso ==, ma non attraverso eql?, quindi:

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

Quindi sei libero di sovrascriverlo per i tuoi usi, oppure puoi scavalcare ==e usare in alias :eql? :==modo che i due metodi si comportino allo stesso modo.

equal? - confronto di identità

Diversamente ==, il equal?metodo non dovrebbe mai essere sovrascritto da sottoclassi: è usato per determinare l'identità dell'oggetto (cioè a.equal?(b)iff aè lo stesso oggetto di b).

Questo è effettivamente un confronto puntatore.


32
Come ho capito dalla tua risposta, la severità è: uguale? <eql? <== <===. Normalmente, usi ==. Per alcuni scopi lenti, usi ===. Per una situazione rigorosa, usi eql? E per l'identità completa usi uguale ?.
sawa,

21
La nozione di rigore non è applicata o addirittura suggerita nella documentazione, ma succede che la Numericgestisce in modo più rigoroso di ==. Dipende davvero dall'autore della classe. ===viene usato raramente al di fuori delle casedichiarazioni.
jtbandes,

4
== è uguaglianza anche in termini di maggiore / minore. Vale a dire, se includi Comparable, verrà definito in termini di <=> restituzione 0. Questo è il motivo per cui 1 == 1.0 restituisce true.
apeiros,

5
@sawa Di solito penso ===che significhi "corrispondenze" (approssimativamente). Come in "regexp corrisponde alla stringa" o "l'intervallo corrisponde (include) il numero".
Kelvin,

7
Curiosità : i documenti ufficiali ora si collegano a questa risposta (vedi ruby-doc.org/core-2.1.5/… ).
Mark Amery,

46

Adoro la risposta di jtbandes, ma poiché è piuttosto lunga, aggiungerò la mia risposta compatta:

==, ===, eql?,equal?
Sono 4 comparatori, cioè. 4 modi per confrontare 2 oggetti, in Ruby.
Dato che in Ruby tutti i comparatori (e la maggior parte degli operatori) sono in realtà chiamate a metodi, è possibile modificare, sovrascrivere e definire da soli la semantica di questi metodi di confronto. Tuttavia, è importante capire quando i costrutti del linguaggio interno di Ruby usano quale comparatore:

==(confronto di valori)
Ruby utilizza: == ovunque per confrontare i valori di 2 oggetti, ad es. Hash-valori:

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

===(caso comparativo)
Ruby usa: === nel caso / quando costruisce. I seguenti frammenti di codice sono logicamente identici:

case foo
  when bar;  p 'do something'
end

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

eql?(Confronto hash-key)
Ruby usa: eql? (in combinazione con il metodo hash) per confrontare i tasti hash. Nella maggior parte delle classi: eql? è identico a: ==.
Conoscenza di: eql? è importante solo quando vuoi creare le tue lezioni speciali:

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

Nota: il set di classe Ruby comunemente usato si basa anche sul confronto di chiavi hash.

equal?(confronto identità oggetto)
utilizza Ruby: uguale? per verificare se due oggetti sono identici. Questo metodo (della classe BasicObject) non deve essere sovrascritto.

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

30
È una buona risposta, ma è lunga quasi quanto quella di jtbandes. :)
odigity

2
@odigity, circa il 70% in più. Potrei pensare a molte cose su cui spendere quel 30%.
Cary Swoveland,

Penso che l'esempio di eql?sia molto fuorviante. eql?è un confronto di uguaglianza coerente con il modo in cui viene calcolato l'hash, ovvero lo a.eql?(b)garantisce a.hash == b.hash. Essa non è sufficiente confrontare i codici hash.
Andrey Tarantsov,

Il confronto tra casi è davvero equivalente bar === fooe no foo === bar? Spero che quest'ultimo sia corretto ed è importante poiché il compilatore chiama il lato sinistro: === ``
Alexis Wilke,

Per quanto ne so, lo è bar === foo: Ruby usa il valore del case sul lato sinistro e la variabile case sul lato destro. Ciò potrebbe avere a che fare con l'evitamento di NPE (Null Pointer Exceptions).
Andreas Rayo Kniep,

34

Operatori di uguaglianza: == e! =

L'operatore ==, noto anche come uguaglianza o doppio uguale, restituirà vero se entrambi gli oggetti sono uguali e falso in caso contrario.

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

L'operatore! =, Noto anche come disuguaglianza, è l'opposto di ==. Restituirà vero se entrambi gli oggetti non sono uguali e falso se sono uguali.

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

Si noti che due matrici con gli stessi elementi in un ordine diverso non sono uguali, le versioni maiuscole e minuscole della stessa lettera non sono uguali e così via.

Quando si confrontano numeri di tipi diversi (ad esempio, intero e float), se il loro valore numerico è lo stesso, == restituirà true.

2 == 2.0 # Output: => true

pari?

A differenza dell'operatore == che verifica se entrambi gli operandi sono uguali, il metodo uguale controlla se i due operandi si riferiscono allo stesso oggetto. Questa è la forma più rigorosa di uguaglianza in Ruby.

Esempio: a = "zen" b = "zen"

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

a.equal? b  # Output: => false

Nell'esempio sopra, abbiamo due stringhe con lo stesso valore. Tuttavia, sono due oggetti distinti, con ID oggetto diversi. Quindi, uguale? il metodo restituirà false.

Riproviamo, solo che questa volta b sarà un riferimento a. Si noti che l'ID oggetto è lo stesso per entrambe le variabili, poiché puntano allo stesso oggetto.

a = "zen"
b = a

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

a.equal? b  # Output: => true

EQL?

Nella classe Hash, l'eql? metodo viene utilizzato per testare le chiavi per l'uguaglianza. Sono necessari alcuni retroscena per spiegare questo. Nel contesto generale dell'informatica, una funzione hash accetta una stringa (o un file) di qualsiasi dimensione e genera una stringa o un numero intero di dimensioni fisse chiamato hashcode, comunemente noto come solo hash. Alcuni tipi di hashcode comunemente usati sono MD5, SHA-1 e CRC. Vengono utilizzati negli algoritmi di crittografia, nell'indicizzazione del database, nel controllo dell'integrità dei file, ecc. Alcuni linguaggi di programmazione, come Ruby, forniscono un tipo di raccolta chiamato tabella hash. Le tabelle hash sono raccolte simili a dizionari che memorizzano i dati in coppie, costituiti da chiavi univoche e dai relativi valori. Sotto il cofano, quei tasti sono memorizzati come hashcode. Le tabelle hash sono comunemente chiamate solo hash. Notare come la parola hashcan si riferisce a un hashcode o a una tabella hash.

Ruby fornisce un metodo integrato chiamato hash per la generazione di hashcode. Nell'esempio seguente, prende una stringa e restituisce un hashcode. Notare come le stringhe con lo stesso valore abbiano sempre lo stesso hashcode, anche se sono oggetti distinti (con ID oggetto diversi).

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

Il metodo hash è implementato nel modulo Kernel, incluso nella classe Object, che è la radice predefinita di tutti gli oggetti Ruby. Alcune classi come Symbol e Integer utilizzano l'implementazione predefinita, altre come String e Hash forniscono le proprie implementazioni.

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

In Ruby, quando memorizziamo qualcosa in un hash (collezione), l'oggetto fornito come chiave (ad es. Stringa o simbolo) viene convertito e memorizzato come hashcode. Successivamente, quando recuperiamo un elemento dall'hash (raccolta), forniamo un oggetto come chiave, che viene convertito in un codice hash e confrontato con le chiavi esistenti. Se esiste una corrispondenza, viene restituito il valore dell'articolo corrispondente. Il confronto viene effettuato utilizzando l'eql? metodo sotto il cofano.

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

Nella maggior parte dei casi, l'eql? Il metodo si comporta in modo simile al metodo ==. Comunque, ci sono alcune eccezioni. Ad esempio, eql? non esegue la conversione di tipo implicita quando si confronta un numero intero con un float.

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

Operatore di uguaglianza dei casi: ===

Molte delle classi incorporate di Ruby, come String, Range e Regexp, forniscono le proprie implementazioni dell'operatore ===, noto anche come case-uguaglianza, tripli uguali o trequali. Poiché è implementato in modo diverso in ogni classe, si comporterà in modo diverso a seconda del tipo di oggetto su cui è stato chiamato. In genere, restituisce true se l'oggetto a destra "appartiene a" o "è un membro di" l'oggetto a sinistra. Ad esempio, può essere utilizzato per verificare se un oggetto è un'istanza di una classe (o una delle sue sottoclassi).

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

Lo stesso risultato può essere ottenuto con altri metodi che sono probabilmente più adatti per il lavoro. Di solito è meglio scrivere codice che sia facile da leggere essendo il più esplicito possibile, senza sacrificare l'efficienza e la concisione.

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

Si noti che l'ultimo esempio ha restituito false perché numeri interi come 2 sono istanze della classe Fixnum, che è una sottoclasse della classe Integer. Il ===, is_a? e istanza_di? i metodi restituiscono true se l'oggetto è un'istanza della classe specificata o delle sottoclassi. Il metodo instance_of è più rigoroso e restituisce true solo se l'oggetto è un'istanza di quella classe esatta, non una sottoclasse.

L'is_a? e tipo_di? i metodi sono implementati nel modulo Kernel, che è mischiato dalla classe Object. Entrambi sono alias dello stesso metodo. Verifichiamo:

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

Intervallo di implementazione di ===

Quando l'operatore === viene chiamato su un oggetto intervallo, restituisce vero se il valore a destra rientra nell'intervallo a sinistra.

(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

Ricordare che l'operatore === invoca il metodo === dell'oggetto a sinistra. Quindi (1..4) === 3 equivale a (1..4). === 3. In altre parole, la classe dell'operando di sinistra definirà quale implementazione del metodo === sarà chiamato, quindi le posizioni degli operandi non sono intercambiabili.

Regexp Implementazione di ===

Restituisce vero se la stringa a destra corrisponde all'espressione regolare a sinistra. / zen / === "pratica zazen oggi" # Output: => true # è uguale a "pratica zazen oggi" = ~ / zen /

Uso implicito dell'operatore === sulle istruzioni case / when

Questo operatore viene utilizzato anche sotto il cofano delle istruzioni case / when. Questo è il suo uso più comune.

minutes = 15

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

# Output: match

Nell'esempio sopra, se Ruby avesse implicitamente usato il doppio operatore uguale (==), l'intervallo 10..20 non sarebbe considerato uguale a un numero intero come 15. Corrispondono perché l'operatore triplo uguale (===) è usato implicitamente in tutte le istruzioni case / when. Il codice nell'esempio sopra è equivalente a:

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

Operatori di corrispondenza dei motivi: = ~ e! ~

Gli operatori = ~ (equal-tilde) e! ~ (Bang-tilde) vengono utilizzati per abbinare stringhe e simboli a schemi regex.

L'implementazione del metodo = ~ nelle classi String e Symbol prevede un'espressione regolare (un'istanza della classe Regexp) come argomento.

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

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

L'implementazione nella classe Regexp prevede una stringa o un simbolo come argomento.

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

In tutte le implementazioni, quando la stringa o il simbolo corrispondono al modello Regexp, restituisce un numero intero che rappresenta la posizione (indice) della corrispondenza. Se non c'è corrispondenza, restituisce zero. Ricorda che, in Ruby, qualsiasi valore intero è "verità" e zero è "falsa", quindi l'operatore = ~ può essere usato in istruzioni if ​​e operatori ternari.

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

Gli operatori di corrispondenza dei modelli sono utili anche per scrivere istruzioni if ​​più brevi. Esempio:

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

L'operatore! ~ È l'opposto di = ~, restituisce vero quando non c'è corrispondenza e falso se c'è una corrispondenza.

Maggiori informazioni sono disponibili in questo post del blog .


6
Trovo che questa sia una risposta migliore della risposta attualmente accettata, in quanto fornisce ottimi esempi ed è meno ambigua su cosa significano i diversi tipi di uguaglianza e perché esistono / dove vengono utilizzati.
Qqwy,

1
Risposta molto dettagliata, ma sul mio irb (ruby v 2.2.1) :zen === "zen"restituisce false
Mike R

@MikeR Grazie per avermelo fatto notare. Ho corretto la risposta.
BrunoFacca,

Penso che intendi tipo_di? "Si noti che l'ultimo esempio ha restituito falso perché numeri interi come 2 sono istanze della classe Fixnum, che è una sottoclasse della classe Integer. Il ===, is_a? E istanza_of? (TYPE_OF?)"?
user1883793,

1
Adoro questa risposta. Grazie
Abdullah Fadhel,


8

=== # --- uguaglianza del caso

== # --- uguaglianza generica

entrambi funzionano in modo simile ma "===" fa anche dichiarazioni di casi

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

qui la differenza

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

3
Essi non funzionano in modo simile, anche se tende ad essere vero che quando a==bpoi a===b. Ma a===bè molto più potente. ===non è simmetrico e a===bsignifica una cosa molto diversa da b===a, figuriamoci a==b.
mwfearnley,

8

Vorrei espandere l' ===operatore.

=== non è un operatore di uguaglianza!

Non.

Facciamo capire davvero questo punto.

Potresti avere familiarità con ===un operatore di uguaglianza in Javascript e PHP, ma questo non è un operatore di uguaglianza in Ruby e ha una semantica sostanzialmente diversa.

Quindi cosa fa ===?

=== è l'operatore di abbinamento dei modelli!

  • === corrisponde alle espressioni regolari
  • === controlla l'intervallo di appartenenza
  • === controlla che sia un'istanza di una classe
  • === chiama espressioni lambda
  • === a volte controlla l'uguaglianza, ma per lo più no

Quindi, come ha senso questa follia?

  • Enumerable#greputilizza ===internamente
  • case whenle dichiarazioni usano ===internamente
  • Fatto divertente, rescueutilizza ===internamente

Questo è il motivo per cui puoi usare espressioni regolari, classi e intervalli e persino espressioni lambda in case whenun'istruzione.

Qualche esempio

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

Tutti questi esempi funzionano pattern === valueanche con , oltre che con il grepmetodo.

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

Ho scritto un semplice test per tutto quanto sopra.

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.