web-dev-qa-db-fra.com

Quelle est la différence entre equal ?, eql ?, === et ==?

J'essaie de comprendre la différence entre ces quatre méthodes. Je sais par défaut que == appelle la méthode equal? qui renvoie la valeur true lorsque les deux opérandes se réfèrent exactement au même objet.

=== par défaut appelle également == qui appelle equal?... d'accord, donc si ces trois méthodes ne sont pas remplacées, alors je suppose que ===, == et equal? fait exactement la même chose?

Maintenant vient eql?. Qu'est-ce que cela fait (par défaut)? Fait-il un appel au hash/id de l'opérande?

Pourquoi Ruby a-t-il autant de signes d'égalité? Sont-ils supposés différer en sémantique?

530
denniss

Je vais citer lourdement la documentation Object ici, car je pense que cela a de très bonnes explications. Je vous encourage à le lire, ainsi qu’à la documentation relative à ces méthodes, car elles sont surchargées dans d’autres classes, comme String .

Note latérale: si vous voulez les essayer vous-même sur différents objets, utilisez quelque chose comme ceci:

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}

== - générique "égalité"

Au niveau de l'objet, == renvoie true uniquement si obj et other sont le même objet. Généralement, cette méthode est substituée dans les classes descendantes pour fournir une signification spécifique à la classe.

C'est la comparaison la plus courante, et donc l'endroit le plus fondamental où vous (en tant qu'auteur d'une classe) décidez si deux objets sont "égaux" ou non.

=== - égalité de cas

Pour la classe Object, identique à l'appel de #==, mais généralement redéfini par les descendants pour fournir une sémantique significative dans les instructions case.

C'est incroyablement utile. Exemples de choses qui ont une implémentation intéressante de ===:

  • Gamme
  • Regex
  • Proc (in Ruby 1.9)

Pour que vous puissiez faire des choses comme:

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

Voir ma réponse ici pour un bel exemple de la façon dont case + Regex peut rendre le code beaucoup plus propre. Et bien sûr, en fournissant votre propre implémentation ===, vous pouvez obtenir une sémantique personnalisée case.

eql? - Hash égalité

La méthode eql? renvoie true si obj et other font référence à la même clé de hachage. Hash l'utilise pour tester l'égalité des membres. Pour les objets de classe Object, eql? est synonyme de ==. Les sous-classes poursuivent normalement cette tradition en appelant eql? à leur méthode surchargée ==, mais il existe des exceptions. Numeric types, par exemple, effectuent une conversion de type sur ==, mais pas sur eql?, ainsi:

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

Vous êtes donc libre de remplacer cela par vos propres utilisations ou vous pouvez remplacer == et utiliser alias :eql? :== afin que les deux méthodes se comportent de la même manière.

equal? - comparaison d'identité

Contrairement à ==, la méthode equal? ne doit jamais être remplacée par des sous-classes: elle est utilisée pour déterminer l'identité de l'objet (c'est-à-dire que a.equal?(b) iff a est le même objet que b.).

C'est effectivement une comparaison de pointeur.

765
jtbandes

J'adore jtbandes, mais comme c'est assez long, j'ajouterai ma propre réponse compacte:

==_, _===_, _eql?_, _equal?
sont 4 comparateurs, à savoir. 4 façons de comparer 2 objets, en Ruby.
Comme dans Ruby, tous les comparateurs (et la plupart des opérateurs) sont en fait des appels de méthode, vous pouvez modifier, écraser et définir vous-même la sémantique de ces méthodes de comparaison. Cependant, il est important de comprendre, lorsque les constructions de langage interne de Ruby utilisent quel comparateur:

_==_ (comparaison de valeur)
Ruby utilise: == partout pour comparer les valeurs de 2 objets, par exemple. Valeurs de hachage:

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

_===_ (comparaison de cas)
Ruby utilise: === dans les constructions case/when. Les extraits de code suivants sont logiquement identiques:

_case foo
  when bar;  p 'do something'
end

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

_eql?_ (comparaison de clé de hachage)
Ruby utilise: eql? (en combinaison avec la méthode hash) pour comparer Hash-keys. Dans la plupart des classes: eql? est identique à: ==.
Connaissance sur: eql? n’est important que lorsque vous voulez créer vos propres classes spéciales:

_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
_

Remarque: le jeu Ruby-class Set généralement utilisé repose également sur la comparaison par hachage.

_equal?_ (comparaison d'identité d'objet)
Ruby utilise: égal? pour vérifier si deux objets sont identiques. Cette méthode (de classe BasicObject) n'est pas supposée être écrasée.

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

Opérateurs d'égalité: == et! =

L'opérateur ==, également appelé égalité ou double égal, renverra true si les deux objets sont égaux et false s'ils ne le sont pas.

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

L'opérateur! =, Également appelé inégalité, est l'opposé de ==. Il retournera vrai si les deux objets ne sont pas égaux et faux si ils sont égaux.

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

Notez que deux tableaux avec les mêmes éléments dans un ordre différent ne sont pas égaux, les versions majuscules et minuscules de la même lettre ne sont pas égales et ainsi de suite.

Lors de la comparaison de nombres de types différents (par exemple, entier et float), si leur valeur numérique est la même, == retournera la valeur true.

2 == 2.0 # Output: => true

égal?

Contrairement à l'opérateur == qui teste si les deux opérandes sont égaux, la méthode equal vérifie si les deux opérandes se rapportent au même objet. C'est la forme la plus stricte d'égalité en Ruby.

Exemple: a = "zen" b = "zen"

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

a.equal? b  # Output: => false

Dans l'exemple ci-dessus, nous avons deux chaînes avec la même valeur. Cependant, il s'agit de deux objets distincts, avec des ID d'objet différents. Par conséquent, l'égal? La méthode retournera false.

Essayons encore, seulement cette fois b sera une référence à a. Notez que l'ID d'objet est le même pour les deux variables, car elles pointent vers le même objet.

a = "zen"
b = a

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

a.equal? b  # Output: => true

eql?

Dans la classe de hachage, l'eql? Cette méthode est utilisée pour tester l’égalité des clés. Un certain fond est nécessaire pour expliquer cela. Dans le contexte général de l'informatique, une fonction de hachage prend une chaîne (ou un fichier) de n'importe quelle taille et génère une chaîne ou un entier de taille fixe appelé hashcode, couramment appelé hachage. Certains types de hashcode couramment utilisés sont MD5, SHA-1 et CRC. Ils sont utilisés dans les algorithmes de chiffrement, l'indexation des bases de données, la vérification de l'intégrité des fichiers, etc. Certains langages de programmation, tels que Ruby, fournissent un type de collection appelé table de hachage. Les tables de hachage sont des collections de type dictionnaire qui stockent des données par paires, composées de clés uniques et de leurs valeurs correspondantes. Sous le capot, ces clés sont stockées sous forme de codes de hachage. Les tables de hachage sont communément appelées simplement des hachages. Notez que le hash Word peut faire référence à un hashcode ou à une table de hachage. Dans le contexte de la programmation Ruby, le hachage Word fait presque toujours référence à la collection de type dictionnaire.

Ruby fournit une méthode intégrée appelée hachage pour générer des codes de hachage. Dans l'exemple ci-dessous, il faut une chaîne et retourne un hashcode. Notez que les chaînes avec la même valeur ont toujours le même hashcode, même s'il s'agit d'objets distincts (avec des ID d'objet différents).

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

La méthode de hachage est implémentée dans le module Kernel, inclus dans la classe Object, qui est la racine par défaut de tous les objets Ruby. Certaines classes telles que Symbol et Integer utilisent l'implémentation par défaut, d'autres telles que String et Hash fournissent leurs propres implémentations.

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, lorsque nous stockons quelque chose dans un hachage (collection), l'objet fourni en tant que clé (par exemple, une chaîne ou un symbole) est converti et stocké sous forme de code de hachage. Plus tard, lors de la récupération d'un élément de la table de hachage (collection), nous fournissons un objet sous forme de clé, qui est convertie en un code de hachage et comparée aux clés existantes. S'il y a correspondance, la valeur de l'élément correspondant est renvoyée. La comparaison est faite en utilisant l'eql? méthode sous le capot.

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

Dans la plupart des cas, l'eql? méthode se comporte de la même manière que la méthode ==. Cependant, il y a quelques exceptions. Par exemple, eql? n'effectue pas de conversion de type implicite lors de la comparaison d'un entier à un float.

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

Opérateur d'égalité de cas: ===

De nombreuses classes intégrées de Ruby, telles que String, Range et Regexp, fournissent leurs propres implémentations de l'opérateur ===, également connu sous le nom d'égalité de cas, triple égal ou triple. Comme il est implémenté différemment dans chaque classe, il se comportera différemment selon le type d'objet sur lequel il a été appelé. Généralement, il retourne vrai si l'objet de droite "appartient à" ou "est un membre de" l'objet de gauche. Par exemple, il peut être utilisé pour tester si un objet est une instance d'une classe (ou de l'une de ses sous-classes).

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

Le même résultat peut être obtenu avec d'autres méthodes qui conviennent probablement le mieux au travail. Il est généralement préférable d'écrire du code facile à lire en étant aussi explicite que possible, sans sacrifier l'efficacité et la concision.

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

Notez que le dernier exemple a renvoyé la valeur false car les entiers tels que 2 sont des instances de la classe Fixnum, qui est une sous-classe de la classe Integer. Le ===, is_a? et instance_of? les méthodes renvoient true si l'objet est une instance de la classe donnée ou de l'une de ses sous-classes. La méthode instance_of est plus stricte et ne renvoie true que si l'objet est une instance de cette classe exacte, pas une sous-classe.

Le is_a? et gentil_de? les méthodes sont implémentées dans le module Kernel, qui est mélangé par la classe Object. Les deux sont des alias de la même méthode. Vérifions:

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

Mise en oeuvre de la gamme de ===

Lorsque l'opérateur === est appelé sur un objet d'intervalle, il renvoie true si la valeur de droite se situe dans l'intervalle de gauche.

(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

Rappelez-vous que l'opérateur === appelle la méthode === de l'objet de gauche. Donc (1..4) === 3 est équivalent à (1..4). === 3. En d’autres termes, la classe de l’opérande de gauche déterminera quelle implémentation de la méthode === sera appelé, donc les positions d'opérande ne sont pas interchangeables.

Regexp Implémentation de ===

Renvoie true si la chaîne à droite correspond à l'expression régulière à gauche./zen/=== "pratiquez zazen aujourd'hui" # Résultat: => vrai # est identique à "pratiquez zazen aujourd'hui" = ~/zen /

Utilisation implicite de l'opérateur === dans les déclarations case/when

Cet opérateur est également utilisé sous le capot dans les déclarations case/when. C'est son utilisation la plus courante.

minutes = 15

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

# Output: match

Dans l'exemple ci-dessus, si Ruby avait implicitement utilisé l'opérateur double égal (==), la plage 10..20 ne serait pas considérée comme égale à un entier tel que 15. Ils correspondent car l'opérateur triple égal (===) est implicitement utilisé dans toutes les déclarations case/when. Le code dans l'exemple ci-dessus est équivalent à:

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

Opérateurs de correspondance de motifs: = ~ et! ~

Les opérateurs = ~ (equal-tilde) et! ~ (Bang-tilde) sont utilisés pour faire correspondre des chaînes et des symboles à des motifs de regex.

L'implémentation de la méthode = ~ dans les classes String et Symbol requiert une expression régulière (une instance de la classe Regexp) en tant qu'argument.

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

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

L'implémentation dans la classe Regexp attend une chaîne ou un symbole en tant qu'argument.

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

Dans toutes les implémentations, lorsque la chaîne ou le symbole correspond au modèle Regexp, il renvoie un entier qui correspond à la position (index) de la correspondance. S'il n'y a pas de correspondance, la valeur renvoyée est nulle. Rappelez-vous que, dans Ruby, toute valeur entière est "truey" et nil est "falsy". L'opérateur = ~ peut donc être utilisé dans les instructions if et les opérateurs ternaires.

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

Les opérateurs de correspondance de modèle sont également utiles pour écrire des instructions if plus courtes. Exemple:

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'opérateur! ~ Est l'opposé de = ~, il retourne vrai lorsqu'il n'y a pas de correspondance et faux s'il y a correspondance.

Plus d'informations sont disponibles sur cet article de blog .

30
BrunoFacca

Ruby expose plusieurs méthodes différentes pour gérer l'égalité:

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.

Continuez à lire en cliquant sur le lien ci-dessous, cela m'a donné une compréhension résumée claire.

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

J'espère que ça aide les autres.

9
kalibbala

Je voudrais développer l'opérateur ===.

=== n'est pas un opérateur d'égalité!

Ne pas.

Faisons passer ce point vraiment.

Vous connaissez peut-être === en tant qu'opérateur d'égalité en Javascript et PHP, mais ce n'est tout simplement pas un opérateur d'égalité dans Ruby et a une sémantique fondamentalement différente.

Alors, que fait ===?

=== est l'opérateur de correspondance de modèle!

  • === correspond aux expressions régulières
  • === vérifie l'appartenance à une plage
  • === vérifie qu'il s'agit d'une instance de classe
  • === appelle des expressions lambda
  • === vérifie parfois l'égalité, mais surtout pas

Alors, comment cette folie a-t-elle un sens?

  • Enumerable#grep utilise === en interne
  • Les instructions case when utilisent === en interne
  • Fait amusant, rescue utilise === en interne

C'est pourquoi vous pouvez utiliser des expressions régulières, des classes, des plages et même des expressions lambda dans une instruction case when.

Quelques exemples

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

Tous ces exemples fonctionnent également avec pattern === value, ainsi qu'avec la méthode 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]
8
akuhn

=== # --- égalité de cas

== # --- égalité générique

les deux fonctionnent de manière similaire mais "===" fait même des déclarations de cas

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

ici la différence

String === "test"   #=> true
String == "test"  #=> false
8
Kishore Mohan