Qual é a diferença entre equal ?, eql ?, === e ==?

552

Estou tentando entender a diferença entre esses quatro métodos. Eu sei por padrão que ==chama o método equal?que retorna true quando ambos os operandos se referem exatamente ao mesmo objeto.

===por padrão também pede ==que as chamadas equal?... bem, por isso, se todos esses três métodos não são substituídos, então eu acho ===, ==e equal?fazer exatamente a mesma coisa?

Agora vem eql?. O que isso faz (por padrão)? Faz uma chamada para o hash / id do operando?

Por que Ruby tem tantos sinais de igualdade? Eles deveriam diferir na semântica?

denniss
fonte
Eu só comecei a IRB e teve o seguinte resultado que contradiz o seu ... Todos estes 3 são verdadeiros: "a" == "a", "a" === "a"e "a".eql? "a". Mas isso é falso: "a".equal? "a"(Minas é ruby 1.9.2-p180)
PeterWong
7
@ Peter: Isso ocorre porque as strings substituem todos os operadores de igualdade. Tentando usar a = Object.new; b = Object.new, em seguida, todos ==, ===, .equal?, .eql?retornará truepara a avs ae false para avs b.
Nemo157

Respostas:

785

Vou citar fortemente a documentação do objeto aqui, porque acho que ela tem ótimas explicações. Convido você a ler e também a documentação desses métodos, pois eles são substituídos em outras classes, como String .

Nota lateral: se você quiser experimentar por conta própria em objetos diferentes, use algo como isto:

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}

== - "igualdade" genérica

No nível do objeto, ==retorna true somente se obje otherfor o mesmo objeto. Normalmente, esse método é substituído nas classes descendentes para fornecer significado específico à classe.

Essa é a comparação mais comum e, portanto, o local mais fundamental onde você (como autor de uma classe) decide se dois objetos são "iguais" ou não.

=== - igualdade de casos

Para a classe Object, efetivamente o mesmo que chamar #==, mas geralmente substituído por descendentes para fornecer semântica significativa nas instruções de caso.

Isso é incrivelmente útil. Exemplos de coisas que têm ===implementações interessantes :

  • Alcance
  • Regex
  • Proc (em Ruby 1.9)

Então você pode fazer coisas 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

Veja minha resposta aqui para um exemplo puro de como case+ Regexpode tornar o código muito mais limpo. E, é claro, fornecendo sua própria ===implementação, você pode obter casesemânticas personalizadas .

eql?- Hashigualdade

O eql?método retorna true se obje se otherrefere à mesma chave de hash. Isso é usado Hashpara testar membros quanto à igualdade. Para objetos de classe Object, eql?é sinônimo de ==. As subclasses normalmente continuam essa tradição usando o alias do método eql?substituído ==, mas há exceções. Numerictipos, por exemplo, realizam a conversão de tipos ==, mas não entre eql?, de modo que:

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

Portanto, você pode substituir isso para seus próprios usos ou pode substituir ==e usar alias :eql? :==para que os dois métodos se comportem da mesma maneira.

equal? - comparação de identidade

Ao contrário ==, o equal?método nunca deve ser substituído por subclasses: é usado para determinar a identidade do objeto (ou seja, a.equal?(b)se ao iff é o mesmo objeto b).

Esta é efetivamente uma comparação de ponteiro.

jtbandes
fonte
32
Pelo que entendi da sua resposta, o rigor é: igual? <eql? <== <===. Normalmente, você usa ==. Para alguns fins soltos, você usa ===. Para uma situação estrita, você usa o eql? E, para uma identidade completa, usa o igual ?.
21412812
21
A noção de rigor não é aplicada nem sugerida na documentação; acontece que a Numericlida com uma maneira mais rigorosa do que ==. É realmente com o autor da classe. ===é raramente usado fora das caseinstruções.
Jtbandes 13/10/12
4
== é a igualdade em termos de maior / menor também. Ou seja, se você incluir Comparável, ele será definido em termos de <=> retornando 0. É por isso que 1 == 1.0 retorna verdadeiro.
Ap4
5
@sawa Eu costumo pensar ===no significado de "correspondências" (aproximadamente). Como em "o regexp corresponde à string" ou "o intervalo corresponde (inclui) ao número".
Kelvin
7
Curiosidade: os documentos oficiais agora apontam para esta resposta (consulte ruby-doc.org/core-2.1.5/… ).
Mark Amery
46

Adoro a resposta do jtbandes, mas como é bastante longo, adicionarei minha própria resposta compacta:

==, ===, eql?,equal?
São 4 comparadores, isto é. 4 maneiras de comparar 2 objetos, em Ruby.
Como, no Ruby, todos os comparadores (e a maioria dos operadores) são na verdade chamadas de método, você pode alterar, substituir e definir a semântica desses métodos de comparação. No entanto, é importante entender quando a linguagem interna do Ruby constrói o uso de qual comparador:

==(comparação de valores) O
Ruby usa: == em qualquer lugar para comparar os valores de 2 objetos, por exemplo. Valores de hash:

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

===(comparação de casos)
Ruby usa: === no caso / quando constrói. Os seguintes trechos de código são logicamente idênticos:

case foo
  when bar;  p 'do something'
end

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

eql?(Comparação de teclas de hash)
Ruby usa: eql? (em combinação com o método hash) para comparar as teclas hash. Na maioria das classes: eql? é idêntico a: ==.
Conhecimento sobre: ​​eql? é importante apenas quando você deseja criar suas próprias classes especiais:

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: O conjunto de classe Ruby comumente usado também depende da comparação de chaves Hash.

equal?(comparação de identidade de objeto)
Ruby usa: equal? para verificar se dois objetos são idênticos. Este método (da classe BasicObject) não deve ser substituído.

obj = obj2 = 'a'
obj.equal? obj2       # => true
obj.equal? obj.dup    # => false
Andreas Rayo Kniep
fonte
30
É uma boa resposta, mas é quase quase do tamanho da jtbandes. :)
odigity
2
@odigity, cerca de 70% do tempo. Eu poderia pensar em muitas coisas para gastar esses 30%.
Cary Swoveland
Eu acho que o exemplo de eql?é muito enganador. eql?é uma comparação de igualdade consistente com a maneira como o hash é calculado, ou seja, a.eql?(b)garante isso a.hash == b.hash. Ele simplesmente não compara os códigos de hash.
Andrey Tarantsov 02/07/19
A comparação de casos é realmente equivalente bar === fooe não foo === bar? Espero que o último esteja correto e seja importante, pois o compilador chama o lado esquerdo: === ''
Alexis Wilke
Até onde eu sei, é bar === foo: Ruby usa o valor do caso no lado esquerdo e a variável do caso no lado direito. Isso pode ter a ver com evitar NPEs (Exceções de ponteiro nulo).
Andreas Rayo Kniep
34

Operadores de igualdade: == e! =

O operador ==, também conhecido como igualdade ou duplo igual, retornará true se os dois objetos forem iguais e false se não forem.

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

O operador! =, Também conhecido como desigualdade, é o oposto de ==. Retornará true se os dois objetos não forem iguais e false se forem iguais.

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

Observe que duas matrizes com os mesmos elementos em uma ordem diferente não são iguais, versões em maiúsculas e minúsculas da mesma letra não são iguais e assim por diante.

Ao comparar números de tipos diferentes (por exemplo, número inteiro e número flutuante), se o valor numérico for o mesmo, == retornará verdadeiro.

2 == 2.0 # Output: => true

igual?

Diferente do operador == que testa se os dois operandos são iguais, o método equal verifica se os dois operandos se referem ao mesmo objeto. Esta é a forma mais estrita de igualdade em Ruby.

Exemplo: a = "zen" b = "zen"

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

a.equal? b  # Output: => false

No exemplo acima, temos duas cadeias com o mesmo valor. No entanto, eles são dois objetos distintos, com diferentes IDs de objeto. Portanto, o igual? O método retornará false.

Vamos tentar novamente, só que desta vez b será uma referência a. Observe que o ID do objeto é o mesmo para as duas variáveis, pois elas apontam para o mesmo objeto.

a = "zen"
b = a

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

a.equal? b  # Output: => true

eql?

Na classe Hash, o eql? método é usado para testar chaves para igualdade. Alguns antecedentes são necessários para explicar isso. No contexto geral da computação, uma função hash pega uma string (ou um arquivo) de qualquer tamanho e gera uma string ou um número inteiro de tamanho fixo chamado hashcode, geralmente chamado apenas de hash. Alguns tipos de código de hash comumente usados ​​são MD5, SHA-1 e CRC. Eles são usados ​​em algoritmos de criptografia, indexação de banco de dados, verificação de integridade de arquivos etc. Algumas linguagens de programação, como Ruby, fornecem um tipo de coleção chamado tabela de hash. As tabelas de hash são coleções semelhantes a dicionários que armazenam dados em pares, consistindo em chaves exclusivas e seus valores correspondentes. Sob o capô, essas chaves são armazenadas como códigos de hash. As tabelas de hash são comumente referidas como apenas hashes. Observe como a palavra hashcan se refere a um hashcode ou a uma tabela de hash.

O Ruby fornece um método interno chamado hash para gerar códigos de hash. No exemplo abaixo, ele pega uma string e retorna um código hash. Observe como seqüências de caracteres com o mesmo valor sempre têm o mesmo código de hash, mesmo que sejam objetos distintos (com IDs de objeto diferentes).

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

O método hash é implementado no módulo Kernel, incluído na classe Object, que é a raiz padrão de todos os objetos Ruby. Algumas classes, como Symbol e Inteiro, usam a implementação padrão, outras como String e Hash fornecem suas próprias implementações.

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

No Ruby, quando armazenamos algo em um hash (coleção), o objeto fornecido como uma chave (por exemplo, string ou símbolo) é convertido e armazenado como um código de hash. Mais tarde, ao recuperar um elemento do hash (coleção), fornecemos um objeto como uma chave, que é convertida em um código de hash e comparada às chaves existentes. Se houver uma correspondência, o valor do item correspondente será retornado. A comparação é feita usando o eql? método sob o capô.

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

Na maioria dos casos, o eql? O método se comporta de maneira semelhante ao método ==. No entanto, existem algumas exceções. Por exemplo, eql? não realiza conversão implícita de tipo ao comparar um número inteiro a um número flutuante.

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

Operador de igualdade de caso: ===

Muitas das classes internas do Ruby, como String, Range e Regexp, fornecem suas próprias implementações do operador ===, também conhecido como igualdade entre maiúsculas e minúsculas, iguais a triplos ou três iguais. Como é implementado de maneira diferente em cada classe, ele se comportará de maneira diferente, dependendo do tipo de objeto em que foi chamado. Geralmente, ele retornará true se o objeto à direita "pertencer a" ou "for um membro" do objeto à esquerda. Por exemplo, ele pode ser usado para testar se um objeto é uma instância de uma classe (ou uma de suas subclasses).

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

O mesmo resultado pode ser alcançado com outros métodos que provavelmente são mais adequados para o trabalho. Geralmente, é melhor escrever um código que seja fácil de ler, sendo o mais explícito possível, sem sacrificar a eficiência e a concisão.

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

Observe que o último exemplo retornou false porque números inteiros como 2 são instâncias da classe Fixnum, que é uma subclasse da classe Integer. O ===, is_a? e instance_of? Os métodos retornam true se o objeto for uma instância da classe especificada ou de qualquer subclasse. O método instance_of é mais rígido e retornará true se o objeto for uma instância dessa classe exata, não uma subclasse.

O is_a? e tipo_de? métodos são implementados no módulo Kernel, que é misturado pela classe Object. Ambos são aliases para o mesmo método. Vamos verificar:

Kernel.instance_method (: kind_of?) == Kernel.instance_method (: is_a?) # Saída: => true

Implementação de intervalo de ===

Quando o operador === é chamado em um objeto de intervalo, ele retorna true se o valor à direita estiver dentro do intervalo à esquerda.

(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

Lembre-se de que o operador === chama o método === do objeto à esquerda. Então (1..4) === 3 é equivalente a (1..4). === 3. Em outras palavras, a classe do operando esquerdo definirá qual implementação do método === será chamado, para que as posições do operando não sejam intercambiáveis.

Implementação Regexp de ===

Retorna true se a sequência à direita corresponder à expressão regular à esquerda. / zen / === "pratique zazen hoje" # Saída: => true # é o mesmo que "pratique zazen hoje" = ~ / zen /

Uso implícito do operador === nas instruções case / when

Este operador também é usado sob o capô nas instruções case / when. Esse é o seu uso mais comum.

minutes = 15

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

# Output: match

No exemplo acima, se Ruby tivesse usado implicitamente o operador duplo igual (==), o intervalo 10..20 não seria considerado igual a um número inteiro como 15. Eles correspondem porque o operador triplo igual (===) é implicitamente usado em todas as declarações case / when. O código no exemplo acima é equivalente a:

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

Operadores de correspondência de padrões: = ~ e! ~

Os operadores = ~ (equal-til) e! ~ (Bang-til) são usados ​​para comparar cadeias e símbolos com padrões regex.

A implementação do método = ~ nas classes String e Symbol espera uma expressão regular (uma instância da classe Regexp) como argumento.

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

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

A implementação na classe Regexp espera uma sequência ou um símbolo como argumento.

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

Em todas as implementações, quando a sequência ou o símbolo corresponde ao padrão Regexp, ele retorna um número inteiro que é a posição (índice) da correspondência. Se não houver correspondência, ele retornará nulo. Lembre-se de que, em Ruby, qualquer valor inteiro é "verdade" e nulo é "falso"; portanto, o operador = ~ pode ser usado nas instruções if e nos operadores ternários.

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

Os operadores de correspondência de padrões também são úteis para escrever instruções if mais curtas. Exemplo:

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

O operador! ~ É o oposto de = ~, retorna true quando não há correspondência e false se houver uma correspondência.

Mais informações estão disponíveis nesta postagem do blog .

BrunoFacca
fonte
6
Acho isso uma resposta melhor do que a resposta atualmente aceita, pois fornece bons exemplos e é menos ambígua sobre o significado dos diferentes tipos de igualdade e por que eles existem / onde são usados.
Qqwy
1
Resposta muito detalhada, mas no meu irb (ruby v 2.2.1) :zen === "zen"retorna falso #
Mike R
@ MikeR Obrigado por me avisar. Eu corrigi a resposta.
BrunoFacca 02/07/2017
Eu acho que você quer dizer type_of? "Observe que o último exemplo retornou false porque números inteiros como 2 são instâncias da classe Fixnum, que é uma subclasse da classe Integer. O ===, is_a? E instance_of? (TYPE_OF?)"?
user1883793
1
Eu amo essa resposta. Obrigado
Abdullah Fadhel
9

Ruby expõe vários métodos diferentes para lidar com a igualdade:

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.

Continue lendo, clicando no link abaixo, isso me deu um entendimento claro e resumido.

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

Espero que ajude os outros.

kalibbala
fonte
8

=== # --- igualdade de casos

== # --- igualdade genérica

ambos funcionam da mesma forma, mas "===" até fazem declarações de caso

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

aqui a diferença

String === "test"   #=> true
String == "test"  #=> false
Kishore Mohan
fonte
3
Eles não funcionam de forma semelhante, embora ele tende a ser verdade que, quando a==bentão a===b. Mas a===bé muito mais poderoso. ===não é simétrico e a===bsignifica uma coisa muito diferente b===a, e muito menos a==b.
Mwfearnley # 16/15
8

Eu gostaria de expandir o ===operador.

=== não é um operador de igualdade!

Não.

Vamos esclarecer esse ponto.

Você pode estar familiarizado ===como um operador de igualdade em Javascript e PHP, mas isso não é um operador de igualdade em Ruby e tem semântica fundamentalmente diferente.

Então o que ===faz?

=== é o operador de correspondência de padrões!

  • === corresponde a expressões regulares
  • === verifica a participação no intervalo
  • === verifica sendo instância de uma classe
  • === chama expressões lambda
  • === às vezes verifica a igualdade, mas principalmente não

Então, como essa loucura faz sentido?

  • Enumerable#grepusa ===internamente
  • case whendeclarações usam ===internamente
  • Curiosidade, rescueusa ===internamente

É por isso que você pode usar expressões regulares e classes e intervalos e até expressões lambda em uma case wheninstrução.

Alguns exemplos

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 esses exemplos também funcionam com pattern === value, assim como com o 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
fonte
-8

Eu escrevi um teste simples para todas as opções acima.

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
fonte