¿Cuál es la diferencia entre igual ?, ¿eql ?, === y ==?

552

Estoy tratando de entender la diferencia entre estos cuatro métodos. Sé por defecto que ==llama al método equal?que devuelve verdadero cuando ambos operandos se refieren exactamente al mismo objeto.

===por defecto también llama a ==qué llamadas equal?... bueno, entonces, si estos tres métodos no se anulan, entonces supongo ===, ¿ ==y equal?hacer exactamente lo mismo?

Ahora viene eql?. ¿Qué hace esto (por defecto)? ¿Hace una llamada al hash / id del operando?

¿Por qué Ruby tiene tantos signos de igualdad? ¿Se supone que difieren en semántica?

denniss
fuente
Me acaba de empezar su IRB y tuvo el siguiente resultado que contradice la suya ... Todos estos 3 son verdaderas: "a" == "a", "a" === "a"y "a".eql? "a". Pero esto es falso: "a".equal? "a"(El mío es rubí 1.9.2-p180)
PeterWong
77
@ Peter: Eso es porque las cadenas anulan todos los operadores de igualdad. Tratando utilizando a = Object.new; b = Object.newentonces todo ==, ===, .equal?, .eql?volverá truea afrente ay falso para avs b.
Nemo157

Respuestas:

785

Voy a citar fuertemente la documentación de Object aquí, porque creo que tiene algunas explicaciones excelentes. Te animo a que lo leas, y también a la documentación de estos métodos, ya que se anulan en otras clases, como String .

Nota al margen: si desea probarlos en diferentes objetos, use algo como esto:

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}

== - "igualdad" genérica

En el nivel de objeto, ==devuelve verdadero solo si objy otherson el mismo objeto. Por lo general, este método se reemplaza en las clases descendientes para proporcionar un significado específico de la clase.

Esta es la comparación más común y, por lo tanto, el lugar más fundamental donde usted (como autor de una clase) puede decidir si dos objetos son "iguales" o no.

=== - igualdad de casos

Para el objeto de clase, efectivamente lo mismo que llamar #==, pero generalmente anulado por los descendientes para proporcionar una semántica significativa en las declaraciones de caso.

Esto es increíblemente útil. Ejemplos de cosas que tienen ===implementaciones interesantes :

  • Rango
  • Regex
  • Proc (en Ruby 1.9)

Entonces puedes hacer cosas como:

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

Vea mi respuesta aquí para ver un claro ejemplo de cómo case+ Regexpuede hacer que el código sea mucho más limpio. Y, por supuesto, al proporcionar su propia ===implementación, puede obtener una casesemántica personalizada .

eql?- Hashigualdad

El eql?método devuelve verdadero si objy se otherrefiere a la misma clave hash. Esto es usado por Hashlos miembros para evaluar la igualdad. Para objetos de clase Object, eql?es sinónimo de ==. Las subclases normalmente continúan esta tradición aliasing eql?a su ==método anulado , pero hay excepciones. Numericlos tipos, por ejemplo, realizan la conversión de tipos a través ==, pero no a través eql?, de modo que:

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

Por lo tanto, puede anular esto para sus propios usos, o puede anular ==y usar alias :eql? :==para que los dos métodos se comporten de la misma manera.

equal? - comparación de identidad

A diferencia ==, las equal?subclases nunca deben anular el método: se utiliza para determinar la identidad del objeto (es decir, a.equal?(b)si f aes el mismo objeto que b).

Esta es efectivamente la comparación de punteros.

jtbandes
fuente
32
Según entiendo por su respuesta, la rigidez es: ¿igual? <eql? <== <===. Normalmente, usas ==. Para algunos propósitos sueltos, utiliza ===. Para una situación estricta, ¿usa eql ?, y para una identidad completa, ¿usa igual?
sawa
21
La noción de rigor no se aplica ni se sugiere en la documentación, simplemente sucede que lo Numericmaneja de una manera más estricta ==. Realmente depende del autor de la clase. ===se usa con poca frecuencia fuera de las casedeclaraciones.
jtbandes
44
== es igualdad en términos de mayor / menor también. Es decir, si incluye Comparable, se definirá en términos de <=> devolviendo 0. Es por eso que 1 == 1.0 devuelve verdadero.
apeiros
55
@sawa Normalmente pienso ===que significa "coincidencias" (aproximadamente). Como en ", la expresión regular coincide con la cadena" o "el rango coincide (incluye) el número".
Kelvin
77
Dato curioso : los documentos oficiales ahora enlazan con esta respuesta (ver ruby-doc.org/core-2.1.5/… ).
Mark Amery
46

Me encanta la respuesta de jtbandes, pero como es bastante larga, agregaré mi propia respuesta compacta:

==, ===, eql?,equal?
Son 4 comparadores, es decir. 4 formas de comparar 2 objetos, en Ruby.
Como, en Ruby, todos los comparadores (y la mayoría de los operadores) son en realidad llamadas de método, puede cambiar, sobrescribir y definir la semántica de estos métodos de comparación usted mismo. Sin embargo, es importante entender, cuando las construcciones de lenguaje interno de Ruby usan qué comparador:

==(comparación de valores)
Ruby usa: == en todas partes para comparar los valores de 2 objetos, por ejemplo. Valores hash:

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

===(comparación de casos)
Ruby usa: === en caso / cuando construye. Los siguientes fragmentos de código son lógicamente idénticos:

case foo
  when bar;  p 'do something'
end

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

eql?(Comparación de clave hash)
Ruby usa: eql? (en combinación con el método hash) para comparar Hash-keys. En la mayoría de las clases: ¿eql? es idéntico a: ==.
Conocimiento sobre: ​​eql? solo es importante cuando quieres crear tus propias clases especiales:

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: El conjunto Ruby-class de uso común también se basa en la comparación de claves hash.

equal?(comparación de identidad de objeto)
Ruby usa: igual? para verificar si dos objetos son idénticos. Se supone que este método (de la clase BasicObject) no se sobrescribe.

obj = obj2 = 'a'
obj.equal? obj2       # => true
obj.equal? obj.dup    # => false
Andreas Rayo Kniep
fuente
30
Es una buena respuesta, pero es casi tan larga como la de jtbandes. :)
Odigity
2
@odigity, alrededor del 70% del tiempo. Podría pensar en muchas cosas para gastar ese 30%.
Cary Swoveland
Creo que el ejemplo de eql?es muy engañoso. eql?es una comparación de igualdad que es consistente con cómo se calcula el hash, es decir, lo a.eql?(b)garantiza a.hash == b.hash. No , no simplemente comparar los códigos hash.
Andrey Tarantsov
¿La comparación de casos es realmente equivalente bar === fooy no foo === bar? Espero que esto último sea correcto y es importante ya que el compilador llama al lado izquierdo: === ``
Alexis Wilke
Hasta donde sé, lo es bar === foo: Ruby usa el valor del caso en el lado izquierdo y la variable del caso en el lado derecho. Esto podría tener que ver con evitar NPE (excepciones de puntero nulo).
Andreas Rayo Kniep
34

Operadores de igualdad: == y! =

El operador ==, también conocido como igualdad o doble igualdad, devolverá verdadero si ambos objetos son iguales y falso si no lo son.

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

El operador! =, También conocido como desigualdad, es lo opuesto a ==. Devolverá verdadero si ambos objetos no son iguales y falso si son iguales.

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

Tenga en cuenta que dos matrices con los mismos elementos en un orden diferente no son iguales, las versiones en mayúsculas y minúsculas de la misma letra no son iguales, etc.

Al comparar números de diferentes tipos (p. Ej., Entero y flotante), si su valor numérico es el mismo, == devolverá verdadero.

2 == 2.0 # Output: => true

¿igual?

A diferencia del operador == que prueba si ambos operandos son iguales, el método igual verifica si los dos operandos se refieren al mismo objeto. Esta es la forma más estricta de igualdad en Ruby.

Ejemplo: a = "zen" b = "zen"

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

a.equal? b  # Output: => false

En el ejemplo anterior, tenemos dos cadenas con el mismo valor. Sin embargo, son dos objetos distintos, con diferentes ID de objeto. Por lo tanto, el igual? El método devolverá falso.

Intentemos nuevamente, solo que esta vez b será una referencia a a. Observe que la ID del objeto es la misma para ambas variables, ya que apuntan al mismo objeto.

a = "zen"
b = a

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

a.equal? b  # Output: => true

eql?

En la clase Hash, el eql? método se utiliza para probar claves para la igualdad. Se requieren algunos antecedentes para explicar esto. En el contexto general de la informática, una función hash toma una cadena (o un archivo) de cualquier tamaño y genera una cadena o número entero de tamaño fijo llamado hashcode, comúnmente conocido como solo hash. Algunos tipos de hashcode comúnmente utilizados son MD5, SHA-1 y CRC. Se utilizan en algoritmos de cifrado, indexación de bases de datos, comprobación de integridad de archivos, etc. Algunos lenguajes de programación, como Ruby, proporcionan un tipo de colección llamada tabla hash. Las tablas hash son colecciones tipo diccionario que almacenan datos en pares, que consisten en claves únicas y sus valores correspondientes. Debajo del capó, esas claves se almacenan como códigos hash. Las tablas hash se conocen comúnmente como solo hashes. Observe cómo la palabra hash puede referirse a un código hash o a una tabla hash.

Ruby proporciona un método incorporado llamado hash para generar códigos hash. En el siguiente ejemplo, toma una cadena y devuelve un código hash. Observe cómo las cadenas con el mismo valor siempre tienen el mismo código hash, aunque sean objetos distintos (con diferentes ID de objeto).

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

El método hash se implementa en el módulo Kernel, incluido en la clase Object, que es la raíz predeterminada de todos los objetos Ruby. Algunas clases como Symbol e Integer usan la implementación predeterminada, otras como String y Hash proporcionan sus propias implementaciones.

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

En Ruby, cuando almacenamos algo en un hash (colección), el objeto proporcionado como clave (por ejemplo, cadena o símbolo) se convierte y almacena como un código hash. Más tarde, al recuperar un elemento del hash (colección), proporcionamos un objeto como clave, que se convierte en un hashcode y se compara con las claves existentes. Si hay una coincidencia, se devuelve el valor del elemento correspondiente. La comparación se realiza utilizando el eql? método debajo del capó.

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

En la mayoría de los casos, el eql? El método se comporta de manera similar al método ==. Sin embargo, hay algunas excepciones. Por ejemplo, eql? no realiza conversión de tipo implícito al comparar un entero con un flotante.

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

Operador de igualdad de casos: ===

Muchas de las clases integradas de Ruby, como String, Range y Regexp, proporcionan sus propias implementaciones del operador ===, también conocido como igualdad de casos, triples iguales o tres iguales. Debido a que se implementa de manera diferente en cada clase, se comportará de manera diferente dependiendo del tipo de objeto al que fue llamado. En general, devuelve verdadero si el objeto de la derecha "pertenece" o "es miembro del" objeto de la izquierda. Por ejemplo, se puede usar para probar si un objeto es una instancia de una clase (o una de sus subclases).

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

Se puede lograr el mismo resultado con otros métodos que probablemente sean los más adecuados para el trabajo. Por lo general, es mejor escribir código que sea fácil de leer siendo lo más explícito posible, sin sacrificar la eficiencia y la concisión.

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

Observe que el último ejemplo devuelve falso porque los enteros como 2 son instancias de la clase Fixnum, que es una subclase de la clase Integer. El ===, es_a? e instancia_de? Los métodos devuelven verdadero si el objeto es una instancia de la clase dada o de cualquier subclase. El método instance_of es más estricto y solo devuelve verdadero si el objeto es una instancia de esa clase exacta, no una subclase.

El is_a? y kind_of? Los métodos se implementan en el módulo Kernel, que se mezcla con la clase Object. Ambos son alias del mismo método. Vamos a verificar:

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

Implementación de rango de ===

Cuando se llama al operador === en un objeto de rango, devuelve verdadero si el valor de la derecha cae dentro del rango de la izquierda.

(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

Recuerde que el operador === invoca el método === del objeto de la izquierda. Entonces (1..4) === 3 es equivalente a (1..4). === 3. En otras palabras, la clase del operando de la izquierda definirá qué implementación del método === será llamado, por lo que las posiciones de los operandos no son intercambiables.

Implementación de expresiones regulares de ===

Devuelve verdadero si la cadena de la derecha coincide con la expresión regular de la izquierda. / zen / === "practica zazen hoy" # Salida: => verdadero # es lo mismo que "practica zazen hoy" = ~ / zen /

Uso implícito del operador === en declaraciones de caso / cuándo

Este operador también se utiliza bajo el capó en las declaraciones de caso / cuándo. Ese es su uso más común.

minutes = 15

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

# Output: match

En el ejemplo anterior, si Ruby hubiera usado implícitamente el operador de doble igualdad (==), el rango 10..20 no se consideraría igual a un número entero como 15. Coinciden porque el operador de triple igualdad (===) es implícitamente utilizado en todas las declaraciones de caso / cuándo. El código en el ejemplo anterior es equivalente a:

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

Operadores de coincidencia de patrones: = ~ y! ~

Los operadores = ~ (igual-tilde) y! ~ (Bang-tilde) se utilizan para unir cadenas y símbolos con patrones de expresiones regulares.

La implementación del método = ~ en las clases String y Symbol espera una expresión regular (una instancia de la clase Regexp) como argumento.

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

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

La implementación en la clase Regexp espera una cadena o un símbolo como argumento.

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

En todas las implementaciones, cuando la cadena o el símbolo coincide con el patrón Regexp, devuelve un número entero que es la posición (índice) de la coincidencia. Si no hay coincidencia, devuelve nulo. Recuerde que, en Ruby, cualquier valor entero es "verdadero" y nulo es "falso", por lo que el operador = ~ puede usarse en declaraciones if y operadores ternarios.

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

Los operadores de coincidencia de patrones también son útiles para escribir sentencias if más cortas. Ejemplo:

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

El operador! ~ Es lo opuesto a = ~, devuelve verdadero cuando no hay coincidencia y falso si hay coincidencia.

Más información está disponible en esta publicación de blog .

BrunoFacca
fuente
66
Considero que esta es una respuesta mejor que la respuesta actualmente aceptada, ya que proporciona buenos ejemplos y es menos ambigua sobre lo que significan los diferentes tipos de igualdad y por qué existen / dónde se usan.
Qqwy
1
Respuesta muy detallada, pero en mi irb (ruby v 2.2.1) :zen === "zen"devuelve falso
Mike R
@MikeR Gracias por hacérmelo saber. He corregido la respuesta.
BrunoFacca
Creo que te refieres a type_of? "Observe que el último ejemplo devuelve falso porque los enteros como 2 son instancias de la clase Fixnum, que es una subclase de la clase Integer. ¿El ===, is_a? E instance_of? (TYPE_OF?)"?
user1883793
1
Me encanta esta respuesta Gracias
Abdullah Fadhel
9

Ruby expone varios métodos diferentes para manejar la igualdad:

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.

Continúe leyendo haciendo clic en el enlace a continuación, me dio una comprensión clara y resumida.

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

Espero que ayude a los demás.

kalibbala
fuente
8

=== # --- igualdad de casos

== # --- igualdad genérica

ambos funcionan de manera similar pero "===" incluso hacen declaraciones de casos

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

aqui la diferencia

String === "test"   #=> true
String == "test"  #=> false
Kishore Mohan
fuente
3
Ellos no funcionan de manera similar, a pesar de que tiende a ser cierto que cuando a==ba continuación a===b. Pero a===bes mucho más poderoso. ===no es simétrico y a===bsignifica algo muy diferente de b===a, y mucho menos a==b.
mwfearnley
8

Me gustaría ampliar el ===operador.

=== no es un operador de igualdad!

No.

Vamos a entender ese punto realmente.

Es posible que esté familiarizado ===como operador de igualdad en Javascript y PHP, pero esto no es un operador de igualdad en Ruby y tiene una semántica fundamentalmente diferente.

Entonces, ¿qué hace ===?

=== es el operador de coincidencia de patrones!

  • === coincide con expresiones regulares
  • === comprueba membresía de rango
  • === comprueba ser instancia de una clase
  • === llama expresiones lambda
  • === a veces verifica la igualdad, pero en general no

Entonces, ¿cómo tiene sentido esta locura?

  • Enumerable#greputiliza ===internamente
  • case whendeclaraciones utilizan ===internamente
  • Dato curioso, rescueutiliza ===internamente

Es por eso que puede usar expresiones regulares y clases y rangos e incluso expresiones lambda en una case whendeclaración.

Algunos ejemplos

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

Todos estos ejemplos también funcionan pattern === valuecon el grepmétodo.

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
fuente
-8

Escribí una prueba simple para todo lo anterior.

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
fuente