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

552

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

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

Vient maintenant eql?. Qu'est-ce que cela fait (par défaut)? Appelle-t-il le hachage / id de l'opérande?

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

denniss
la source
Je viens de commencer un RIR et a le résultat suivant qui contredit le vôtre ... Tous ces 3 sont vraies: "a" == "a", "a" === "a"et "a".eql? "a". Mais c'est faux: "a".equal? "a"(Le mien est rubis 1.9.2-p180)
PeterWong
7
@Peter: En effet, les chaînes remplacent tous les opérateurs d'égalité. Essayez d' utiliser a = Object.new; b = Object.newalors tout ==, ===, .equal?, .eql?retournera truepour avs aet faux pour avs b.
Nemo157

Réponses:

785

Je vais citer abondamment la documentation Object ici, car je pense qu'elle a de bonnes explications. Je vous encourage à le lire, ainsi que la documentation de ces méthodes car elles sont remplacées dans d'autres classes, comme String .

Note latérale: si vous voulez les essayer par 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}

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

Au niveau de l'objet, ==renvoie vrai uniquement si objet othersont le même objet. En règle générale, cette méthode est remplacé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) pouvez décider si deux objets sont "égaux" ou non.

=== - égalité des cas

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

C'est incroyablement utile. Exemples de choses qui ont des ===implémentations intéressantes :

  • Intervalle
  • Regex
  • Proc (dans Ruby 1.9)

Vous pouvez donc 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 exemple clair de la façon dont case+ Regexpeut rendre le code beaucoup plus propre. Et bien sûr, en fournissant votre propre ===implémentation, vous pouvez obtenir une casesémantique personnalisée .

eql?- Hashégalité

La eql?méthode renvoie true if objet fait otherréférence à la même clé de hachage. Il est utilisé par Hashpour tester l'égalité des membres. Pour les objets de classe Object, eql?est synonyme de ==. Les sous-classes poursuivent normalement cette tradition en appliquant un alias eql?à leur ==méthode remplacée , mais il existe des exceptions. Numericles types, par exemple, effectuent une conversion de type entre ==, mais pas entre eql?, donc:

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

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

equal? - comparaison d'identité

Contrairement à ==la equal?méthode , la méthode 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, a.equal?(b)si siff aest le même objet que b).

Il s'agit en fait d'une comparaison de pointeurs.

jtbandes
la source
32
Si je comprends bien votre réponse, la rigueur est: égale? <eql? <== <===. Normalement, vous utilisez ==. Pour certains objectifs lâches, vous utilisez ===. Pour une situation stricte, vous utilisez eql?, Et pour une identité complète, vous utilisez égal?.
sawa
21
La notion de rigueur n'est pas appliquée ou même suggérée dans la documentation, il se trouve que Numericcela la gère d'une manière plus stricte que ==. C'est vraiment à l'auteur de la classe. ===est rarement utilisé en dehors des casedéclarations.
jtbandes
4
== est égalité en termes de plus grand / plus petit aussi. C'est-à-dire que si vous incluez Comparable, il sera défini en termes de <=> retour de 0. C'est pourquoi 1 == 1.0 renvoie vrai.
apeiros
5
@sawa Je pense généralement ===comme signifiant "correspond" (grosso modo). Comme dans ", l'expression rationnelle correspond-elle à la chaîne" ou "la plage correspond-elle (inclut-elle) au nombre".
Kelvin
7
Fait amusant: les documents officiels contiennent désormais un lien vers cette réponse (voir ruby-doc.org/core-2.1.5/… ).
Mark Amery
46

J'aime la réponse jtbandes, mais comme elle est assez longue, j'ajouterai ma propre réponse compacte:

==, ===, eql?,equal?
Y a 4 comparateurs, ie. 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, remplacer 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 valeurs)
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 par clé de hachage)
Ruby utilise: eql? (en combinaison avec la méthode de hachage) pour comparer les touches de hachage. Dans la plupart des cours: eql? est identique à: ==.
Connaissance de: eql? n'est important que lorsque vous souhaitez 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: L'ensemble de classe Ruby couramment utilisé repose également sur la comparaison de clé de hachage.

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

obj = obj2 = 'a'
obj.equal? obj2       # => true
obj.equal? obj.dup    # => false
Andreas Rayo Kniep
la source
30
C'est une bonne réponse, mais elle est presque aussi longue que celle de jtbandes. :)
odeur
2
@odigity, environ 70% de long. Je pourrais penser à beaucoup de choses pour lesquelles dépenser ces 30%.
Cary Swoveland
Je pense que l'exemple de eql?est très trompeur. eql?est une comparaison d'égalité qui est cohérente avec la façon dont le hachage est calculé, c'est-à-dire a.eql?(b)qui le garantit a.hash == b.hash. Il ne compare pas simplement les codes de hachage.
Andrey Tarantsov
La comparaison des cas est-elle vraiment équivalente bar === fooet non foo === bar? J'espère que ce dernier est correct et qu'il est important car le compilateur appelle le côté gauche: === ``
Alexis Wilke
Pour autant que je sache, c'est bar === foo: Ruby utilise la valeur de casse sur le côté gauche et la variable de casse sur le côté droit. Cela peut être lié à l'évitement des NPE (Null Pointer Exceptions).
Andreas Rayo Kniep
34

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

L'opérateur ==, également appelé égal 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 connu sous le nom d'inégalité, est l'opposé de ==. Il retournera vrai si les deux objets ne sont pas égaux et faux s'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.

Lorsque vous comparez des nombres de différents types (par exemple, entier et flottant), si leur valeur numérique est la même, == renvoie true.

2 == 2.0 # Output: => true

égal?

Contrairement à l'opérateur == qui teste si les deux opérandes sont égaux, la méthode égale vérifie si les deux opérandes se réfèrent au même objet. Il s'agit de la forme d'égalité la plus stricte 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, ce sont deux objets distincts, avec des ID d'objet différents. Par conséquent, l'égal? 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 Hash, l'eql? méthode 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, communément appelé uniquement hachage. Certains types de codes de hachage 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 les 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. Remarquez comment le mot hachage peut faire référence à un code de hachage ou à une table de hachage.

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 prend une chaîne et retourne un hashcode. Notez que les chaînes ayant la même valeur ont toujours le même code de hachage, 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 comme 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

Dans Ruby, lorsque nous stockons quelque chose dans un hachage (collection), l'objet fourni sous forme de clé (par exemple, une chaîne ou un symbole) est converti et stocké en tant que code de hachage. Plus tard, lors de la récupération d'un élément du hachage (collection), nous fournissons un objet sous forme de clé, qui est converti en code de hachage et comparé aux clés existantes. En cas de 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? se comporte de manière similaire à la méthode ==. Il existe cependant quelques exceptions. Par exemple, eql? n'effectue pas de conversion de type implicite lors de la comparaison d'un entier à un flottant.

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

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

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 casse, triple égal ou triple. Parce qu'il est implémenté différemment dans chaque classe, il se comportera différemment selon le type d'objet auquel il a été appelé. Généralement, elle renvoie true si l'objet de droite "appartient à" ou "est membre de" l'objet de gauche. Par exemple, il peut être utilisé pour tester si un objet est une instance d'une classe (ou 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 sont probablement les mieux adaptées 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é 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 des 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.

L'is_a? et kind_of? 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

Implémentation de la gamme ===

Lorsque l'opérateur === est appelé sur un objet de plage, il renvoie vrai si la valeur de droite se situe dans la plage 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

N'oubliez pas 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éfinira quelle implémentation de la méthode === sera appelé, donc les positions d'opérande ne sont pas interchangeables.

Mise en œuvre regexp de ===

Renvoie true si la chaîne de droite correspond à l'expression régulière de gauche. / zen / === "pratiquer zazen aujourd'hui" # Sortie: => true # est le même que "pratiquer zazen aujourd'hui" = ~ / zen /

Utilisation implicite de l'opérateur === sur les instructions case / when

Cet opérateur est également utilisé sous le capot sur 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 parce que l'opérateur triple égal (===) est implicitement utilisé dans toutes les instructions case / when. Le code de l'exemple ci-dessus équivaut à:

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

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

Les opérateurs = ~ (égal-tilde) et! ~ (Bang-tilde) sont utilisés pour faire correspondre les chaînes et les symboles aux motifs d'expression régulière.

L'implémentation de la méthode = ~ dans les classes String et Symbol attend une expression régulière (une instance de la classe Regexp) comme 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 comme 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 est la position (index) de la correspondance. S'il n'y a pas de correspondance, il renvoie zéro. Souvenez-vous que, dans Ruby, toute valeur entière est "true" et nil est "falsy", donc l'opérateur = ~ peut ê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 filtrage 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 renvoie vrai quand il n'y a pas de correspondance et faux s'il y a une correspondance.

Plus d'informations sont disponibles sur ce blog .

BrunoFacca
la source
6
Je trouve que c'est une meilleure réponse que la réponse actuellement acceptée, car elle fournit de bons exemples et est moins ambiguë sur ce que signifient les différents types d'égalité et pourquoi ils existent / où ils sont utilisés.
Qqwy
1
Réponse très détaillée, mais sur mon irb (ruby v 2.2.1) :zen === "zen"retourne faux
Mike R
@MikeR Merci de me le faire savoir. J'ai corrigé la réponse.
BrunoFacca
Je pense que vous voulez dire type_of? "Remarquez que le dernier exemple a renvoyé false parce que 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? (TYPE_OF?)"?
user1883793
1
J'adore cette réponse. Merci
Abdullah Fadhel
9

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 cela aide les autres.

Kalibbala
la source
8

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

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

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

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

ici la différence

String === "test"   #=> true
String == "test"  #=> false
Kishore Mohan
la source
3
Ils ne fonctionnent pas de la même manière, même si cela a tendance à être vrai à ce moment- a==ba===b. Mais a===best beaucoup plus puissant. ===n'est pas symétrique et a===bsignifie une chose très différente de b===a, encore moins a==b.
mwfearnley
8

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

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

Ne pas.

Faisons vraiment passer ce point.

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

Alors qu'est-ce que ça ===fait?

=== est l'opérateur de correspondance de motifs!

  • === correspond aux expressions régulières
  • === vérifie l'appartenance à la plage
  • === vérifie être une instance d'une classe
  • === appelle des expressions lambda
  • === vérifie parfois l'égalité, mais surtout pas

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

  • Enumerable#greputilise en ===interne
  • case wheninstructions utilisées en ===interne
  • Fait amusant, rescueutilise en ===interne

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

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 pattern === valueaussi avec la grepméthode.

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
la source
-8

J'ai écrit un test simple pour tout ce qui précède.

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
la source