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

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

=== di default chiama anche == che chiama equal? … okay, quindi se tutti e tre questi metodi non sono sovrascritti, allora suppongo che === , == e equal? fare esattamente la stessa cosa?

Ora arriva eql? . Cosa fa questo (di default)? Effettua una chiamata all’hash hash dell’operando?

Perché Ruby ha così tanti segni di uguaglianza? Devono differire nella semantica?

    Citerò pesantemente la documentazione Object qui, perché penso che abbia delle ottime spiegazioni. Vi incoraggio a leggerlo, e anche la documentazione di questi metodi come sono sovrascritti in altre classi, come String .

    Nota a margine: se vuoi provarli tu stesso su oggetti diversi, usa qualcosa di simile a questo:

     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 dell’object, == restituisce true solo se obj e other sono lo stesso object. In genere, questo metodo viene sovrascritto nelle classi discendenti per fornire un significato specifico della class.

    Questo è il confronto più comune, e quindi il posto più fondamentale in cui tu (come l’autore di una class) puoi decidere se due oggetti sono “uguali” o meno.

    === – caso l’uguaglianza

    Per object di class, in pratica equivale a chiamare #== , ma in genere sovrascritto dai discendenti per fornire semantica significativa nelle istruzioni caso.

    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 esempio chiaro di come il case + Regex può rendere il codice molto più pulito. E, naturalmente, fornendo la propria implementazione === , è ansible ottenere una semantica del case personalizzata.

    eql?Hash ugality

    L’ eql? il metodo restituisce true se obj e other riferiscono alla stessa chiave hash. Questo è usato da Hash per testare i membri per l’uguaglianza. Per oggetti di class Object , eql? è sinonimo di == . Le sottoclassi continuano normalmente questa tradizione tramite l’aliasing di eql? al loro metodo == sovrascritto, ma ci sono delle eccezioni. Numeric tipi Numeric , ad esempio, eseguono la conversione del tipo su == , ma non su eql? , così:

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

    Quindi sei libero di sovrascriverlo per i tuoi stessi usi, oppure puoi sovrascrivere == e usare alias :eql? :== alias :eql? :== quindi i due metodi si comportano allo stesso modo.

    equal? – Confronto di id quadro

    A differenza di == , equal? il metodo non dovrebbe mai essere sovrascritto dalle sottoclassi: è usato per determinare l’identity framework dell’object (cioè, a.equal?(b) se a è lo stesso object di b ).

    Questo è in effetti un confronto tra puntatori.

    Amo la risposta di jtbandes, ma dato che è piuttosto lunga, aggiungerò la mia risposta compatta:

    == , === , eql? equal?
    sono 4 comparatori, es. 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 al metodo, è ansible modificare, sovrascrivere e definire la semantica di questi metodi di confronto da soli. Tuttavia, è importante capire, quando i costrutti del linguaggio interno di Ruby usano quale comparatore:

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

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

    === (confronto dei casi)
    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 chiave hash)
    Ruby usa: eql? (in combinazione con l’hash del metodo) per confrontare le chiavi di hash. Nella maggior parte delle classi: eql? è identico a: ==.
    Conoscenza: eql? è importante solo quando vuoi creare le tue classi 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 classi Ruby di uso comune si basa anche sul confronto tra chiavi di hash.

    equal? (confronto dell’id quadro dell’object)
    Ruby usa: uguale? per verificare se due oggetti sono identici. Questo metodo (della class BasicObject) non dovrebbe essere sovrascritto.

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

    Operatori di uguaglianza: == e! =

    L’operatore ==, noto anche come uguaglianza o double equal, restituirà true se entrambi gli oggetti sono uguali e false se non lo sono.

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

    L’operatore! =, Disuguaglianza di AKA o bang-tilde, è l’opposto di ==. Restituisce vero se entrambi gli oggetti non sono uguali e falsi se sono uguali.

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

    Si noti che due array 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 equal controlla se i due operandi si riferiscono allo stesso object. 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 object diversi. Quindi, uguale? il metodo restituirà false.

    Proviamo di nuovo, solo che questa volta b sarà un riferimento a a. Si noti che l’ID object è lo stesso per entrambe le variabili, poiché puntano allo stesso object.

     a = "zen" b = a a.object_id # Output: => 18637360 b.object_id # Output: => 18637360 a.equal? b # Output: => true 

    EQL?

    Nella class di hash, l’eql? metodo è usato per testare le chiavi per l’uguaglianza. Per spiegarlo è necessario un po ‘di background. Nel contesto generale dell’informatica, una funzione di hash prende una stringa (o un file) di qualsiasi dimensione e genera una stringa o un intero di dimensione fissa chiamata hashcode, comunemente indicato come solo hash. Alcuni tipi di hash 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 di tipo dizionario che memorizzano i dati in coppie, costituiti da chiavi univoche e valori corrispondenti. Sotto il cofano, quelle chiavi sono memorizzate come hashcode. Le tabelle hash vengono comunemente chiamate solo hash. Si noti come la parola hash si riferisca ad un hashcode o ad una tabella hash. Nel contesto della programmazione di Ruby, la parola hash si riferisce quasi sempre alla raccolta di tipo dizionario.

    Ruby fornisce un metodo integrato chiamato hash per generare hashcode. Nell’esempio seguente, prende una stringa e restituisce un codice hash. Si noti che le stringhe con lo stesso valore hanno sempre lo stesso codice hash, anche se sono oggetti distinti (con ID object diversi).

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

    Il metodo hash è implementato nel modulo Kernel, incluso nella class Object, che è la root 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 (raccolta), l’object fornito come chiave (ad es. Stringa o simbolo) viene convertito e memorizzato come codice hash. Successivamente, quando si recupera un elemento dall’hash (raccolta), viene fornito un object come chiave, che viene convertito in un codice hash e confrontato con le chiavi esistenti. Se c’è una corrispondenza, viene restituito il valore dell’articolo corrispondente. Il confronto è fatto usando 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 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 di casi: ===

    Molte delle classi built-in di Ruby, come String, Range e Regexp, forniscono le proprie implementazioni dell’operatore ===, noto anche come case-ugality, triple equals o threequals. Poiché è implementato in modo diverso in ogni class, si comporterà in modo diverso a seconda del tipo di object su cui è stato chiamato. Generalmente, restituisce true se l’object a destra “appartiene a” o “è un membro di” l’object a sinistra. Ad esempio, può essere usato per verificare se un object è un’istanza di una class (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 raggiunto 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 ansible, senza sacrificare efficienza e concisione.

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

    Si noti l’ultimo esempio restituito falso perché gli interi come 2 sono istanze della class Fixnum, che è una sottoclass della class Integer. Il ===, is_a? e instance_of? i metodi restituiscono true se l’object è un’istanza della class data o qualsiasi sottoclass. Il metodo instance_of è più restrittivo e restituisce true solo se l’object è un’istanza di quella class esatta, non una sottoclass.

    L’is_a? e kind_of? i metodi sono implementati nel modulo Kernel, che è mescolato dalla class Object. Entrambi sono alias per lo stesso metodo. Verifichiamo:

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

    Implementazione gamma di ===

    Quando l’operatore === viene chiamato su un object intervallo, restituisce true 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 

    Ricorda che l’operatore === richiama il metodo === dell’object a sinistra. Quindi (1..4) === 3 è equivalente a (1..4). === 3. In altre parole, la class dell’operando di sinistra definirà quale implementazione del metodo === sarà chiamato, quindi le posizioni degli operandi non sono intercambiabili.

    Implementazione di Regexp ===

    Restituisce true se la stringa a destra corrisponde all’espressione regolare a sinistra. / zen / === “pratica zazen oggi” # Output: => true # è lo stesso di “practice zazen oggi” = ~ / zen /

    Uso implicito dell’operatore === sulle istruzioni caso / quando

    Questo operatore viene anche utilizzato sotto il cofano in caso / quando dichiarazioni. Questo è il suo uso più comune.

     minutes = 15 case minutes when 10..20 puts "match" else puts "no match" end # Output: match 

    Nell’esempio precedente, se Ruby avesse implicitamente utilizzato l’operatore double equal (==), l’intervallo 10..20 non sarebbe considerato uguale a un numero intero come 15. Corrispondono perché l’operatore triple equal (===) è implicitamente utilizzato in tutti i casi / quando le dichiarazioni. Il codice nell’esempio sopra è equivalente a:

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

    Operatori di corrispondenza del modello: = ~ e! ~

    Gli operatori = ~ (equal-tilde) e! ~ (Bang-tilde) vengono utilizzati per associare stringhe e simboli ai modelli regex.

    L’implementazione del metodo = ~ nelle classi String e Symbol prevede un’espressione regolare (un’istanza della class 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 class 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 corrisponde al modello Regexp, restituisce un numero intero che è la posizione (indice) della corrispondenza. Se non c’è corrispondenza, restituisce zero. Ricorda che, in Ruby, qualsiasi valore intero è “verità” e nil è “falsy”, quindi l’operatore = ~ può essere utilizzato 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 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 true quando non c’è corrispondenza e false se c’è una corrispondenza.

    Maggiori informazioni sono disponibili su questo post del blog .

    === # — uguaglianza di casi

    == # — uguaglianza generica

    entrambi funzionano in modo simile, ma “===” fanno anche dichiarazioni sui casi

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

    qui la differenza

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

    Ruby espone diversi metodi per gestire l’uguaglianza:

    a.equival? (b) # id quadro dell’object – a e b si riferiscono allo stesso object

    a.eql? (b) # equivalenza dell’object – a e b hanno lo stesso valore

    a == b # equivalenza di oggetti – a e b hanno lo stesso valore con la conversione di tipo.

    Continua a leggere cliccando sul link sottostante, mi ha dato una chiara comprensione riassuntiva.

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

    Spero che aiuti gli altri.

    Mi piacerebbe espandere l’operatore === .

    === non è un operatore di uguaglianza!

    Non.

    Prendiamo davvero questo punto.

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

    Quindi cosa fa === ?

    === è l’operatore di corrispondenza del modello!

    • === corrisponde alle espressioni regolari
    • === controlla l’appartenenza all’intervallo
    • === verifica l’istanza di una class
    • === chiama espressioni lambda
    • === volte controlla l’uguaglianza, ma soprattutto non lo fa

    Quindi, come ha senso questa pazzia?

    • Enumerable#grep usa internamente ===
    • case when dichiarazioni utilizzano === internamente
    • Fatto divertente, usi di rescue === internamente

    Questo è il motivo per cui puoi usare espressioni regolari, classi, intervalli e persino espressioni lambda nel case when dichiarazione.

    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 anche con il pattern === value , così come con il metodo 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] 

    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)