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

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 == cuál llama equal? … bien, entonces, si no se anulan estos tres métodos, entonces supongo que === , == e equal? hacer exactamente lo mismo?

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

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

Voy a citar en gran medida la documentación del objeto aquí, porque creo que tiene algunas grandes explicaciones. Le recomiendo que lo lea, y también la documentación de estos métodos, ya que están anulados en otras clases, como String .

Nota al margen: si quiere probar esto 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 Object, == devuelve verdadero solo si obj y other son el mismo objeto. Normalmente, este método se reemplaza en las clases descendientes para proporcionar un significado específico de clase.

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

=== – caso de igualdad

Para Objeto de clase, efectivamente es lo mismo que llamar a #== , pero normalmente los reemplazan los descendientes para proporcionar una semántica significativa en las sentencias de caso.

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

  • Distancia
  • 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 un Regex ejemplo de cómo Regex + Regex puede hacer que el código sea mucho más limpio. Y, por supuesto, al proporcionar su propia implementación === , puede obtener una semántica de case personalizada.

eql?Hash igualdad

El eql? el método devuelve verdadero si obj y other refieren a la misma clave hash. Esto lo usa Hash para probar a los miembros por 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. Numeric tipos Numeric , por ejemplo, realizan una conversión de tipo en == , pero no en eql? , asi que:

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

¿Así que puede anular esto para sus propios usos, o puede anular == y usar alias :eql? :== alias :eql? :== entonces los dos métodos se comportan de la misma manera.

equal? – comparación de identidad

A diferencia de == , ¿el equal? El método nunca debe ser anulado por subclases: se usa para determinar la identidad del objeto (es decir, a.equal?(b) si a es el mismo objeto que b ).

Esto es efectivamente una comparación de punteros.

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, usted mismo puede cambiar, sobrescribir y definir la semántica de estos métodos de comparación. Sin embargo, es importante entender, cuando las construcciones del lenguaje interno de Ruby usan qué comparador:

== (comparación de valor)
Ruby usa: == en todas partes para comparar los valores de 2 objetos, ej. Hash-valores:

 {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 las teclas Hash. En la mayoría de las clases: ¿eql? es idéntico con: ==.
Conocimiento sobre: ​​eql? solo es importante, cuando desee crear sus 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 de clase de Ruby comúnmente utilizado también se basa en la comparación de clave hash.

equal? (comparación de identidad de objeto)
Ruby usa: ¿igual? para verificar si dos objetos son idénticos. Este método (de la clase BasicObject) no debe sobrescribirse.

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

Operadores de igualdad: == y! =

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

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

El operador! =, AKA inequality o bang-tilde, es lo contrario de ==. Devolverá verdadero si ambos objetos no son iguales y falsos 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úscula y minúscula de la misma letra no son iguales, y así sucesivamente.

Al comparar números de diferentes tipos (por ejemplo, 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.

Probemos de nuevo, solo que esta vez b será una referencia a a. Tenga en cuenta que el ID del objeto es el mismo 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, ¿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 un entero de tamaño fijo llamado hashcode, comúnmente conocido como hash. Algunos tipos de código hash de uso común son MD5, SHA-1 y CRC. Se usan en algoritmos de encriptación, indexación de bases de datos, verificación de integridad de archivos, etc. Algunos lenguajes de progtwigción, como Ruby, proporcionan un tipo de colección llamado tabla hash. Las tablas Hash son colecciones tipo diccionario que almacenan datos por parejas, que consisten en claves únicas y sus valores correspondientes. Bajo el capó, esas claves se almacenan como códigos hash. Las tablas Hash se conocen comúnmente como hashes simples. Observe cómo la palabra hash se refiere a un código hash o a una tabla hash. En el contexto de la progtwigción de Ruby, la palabra hash casi siempre se refiere a la colección tipo diccionario.

Ruby proporciona un método incorporado llamado hash para generar hashcodes. En el ejemplo siguiente, 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 y Integer, utilizan la implementación predeterminada, mientras que 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 se almacena como código hash. Más tarde, al recuperar un elemento del hash (colección), proporcionamos un objeto como clave, que se convierte en un código hash y se compara con las claves existentes. Si hay una coincidencia, se devuelve el valor del artículo correspondiente. La comparación se realiza utilizando el eql? método bajo el 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? 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ícita al comparar un número entero con un número flotante.

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

Operador de igualdad de caso: ===

Muchas de las clases incorporadas de Ruby, como String, Range y Regexp, proporcionan sus propias implementaciones del operador ===, también conocido como igualdad de casos, triple equals o threequals. Debido a que se implementa de manera diferente en cada clase, se comportará de manera diferente según el tipo de objeto al que se invocó. En general, devuelve verdadero si el objeto de la derecha “pertenece” o “es un miembro de” el 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 

El mismo resultado se puede lograr con otros métodos que probablemente sean los más adecuados para el trabajo. Por lo general, es mejor escribir un código que sea fácil de leer al ser 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 devolvió false porque los enteros como 2 son instancias de la clase Fixnum, que es una subclase de la clase Integer. ¿El ===, is_a? y instance_of? los métodos devuelven verdadero si el objeto es una instancia de la clase dada o 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 está mezclado por la clase Object. Ambos son alias para el mismo método. Verifiquemos:

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 del operando no son intercambiables.

Implementación Regexp 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 las sentencias case / when

Este operador también se usa debajo del capó en caso / cuando las declaraciones. 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 utilizado implícitamente el operador de doble igual (==), el rango 10 … 20 no se consideraría igual a un entero como 15. Coinciden porque el operador de igualdad triple (===) es usado implícitamente en todos los casos / cuando las declaraciones. 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 hacer coincidir 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 coinciden con el patrón Regexp, devuelve un número entero que es la posición (índice) de la coincidencia. Si no hay coincidencia, devuelve nil. Recuerde que, en Ruby, cualquier valor entero es “verdadero” y nulo es “falso”, por lo que el operador = ~ puede usarse en sentencias 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 cortas si. 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 el opuesto de = ~, devuelve verdadero cuando no hay coincidencia y falso si hay una coincidencia.

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

=== # — caso de igualdad

== # — igualdad genérica

ambos funcionan de forma similar pero “===” incluso hacen declaraciones de casos

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

aquí la diferencia

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

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

a.equal? ​​(b) # identidad de objeto – a y b se refieren al mismo objeto

a.eql? (b) # equivalencia del objeto – a y b tienen el mismo valor

a == b # equivalencia del objeto – a y b tienen el mismo valor con la conversión de tipo.

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

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

Espero que ayude a otros.

Me gustaría ampliar el operador === .

=== no es un operador de igualdad!

No.

Lleguemos a ese punto realmente al otro lado.

Es posible que esté familiarizado con === como operador de igualdad en JavaScript y PHP, pero esto simplemente 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
  • === verifica la membresía de rango
  • === verifica la instancia de una clase
  • === llama expresiones lambda
  • === veces verifica la igualdad, pero la mayoría no lo hace

Entonces, ¿cómo tiene sentido esta locura?

  • Enumerable#grep usa === internamente
  • case when declaraciones usan === internamente
  • Dato curioso, el rescue usa internamente ===

Es por eso que puede usar expresiones regulares y clases y rangos e incluso expresiones lambda en una sentencia case when .

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 funcionan con el pattern === value también, así como con el método 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] 

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)