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

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

=== por padrão também chama == que chama equal? … ok, então se todos esses três methods não forem sobrescritos, 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 o Ruby tem tantos sinais de igualdade? Eles deveriam diferir em semântica?

Eu vou citar fortemente a documentação do Object aqui, porque eu acho que tem algumas ótimas explicações. Encorajo-o a lê-lo e também a documentação para esses methods, pois eles são substituídos em outras classs, como String .

Nota lateral: se você quiser experimentar esses objects diferentes, use algo assim:

 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} 

== – genérica “igualdade”

No nível do Objeto, == retorna true somente se obj e other forem o mesmo object. Normalmente, esse método é substituído em classs descendentes para fornecer um significado específico da class.

Esta é a comparação mais comum e, portanto, o lugar mais fundamental em que você (como autor de uma class) decide se dois objects são “iguais” ou não.

=== – igualdade de casos

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

Isso é incrivelmente útil. Exemplos de coisas que possuem 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 de como case + Regex pode tornar o código muito mais limpo. E, claro, ao fornecer sua própria implementação === , você pode obter semânticas de case personalizados.

eql? – Igualdade de Hash

O eql? método retorna true se obj e other referirem à mesma chave hash. Isso é usado pelo Hash para testar membros quanto à igualdade. Para objects da class Object , eql? é sinônimo de == . As subclasss normalmente continuam essa tradição eql? ao seu método == substituído, mas há exceções. Tipos Numeric , por exemplo, realizam conversão de tipo em == , mas não em eql? , assim:

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

Então você está livre para replace isso para seus próprios usos, ou você pode sobrescrever == e usar o alias :eql? :== alias :eql? :== então os dois methods se comportam da mesma maneira.

equal? – comparação de identidade

Ao contrário de == , o equal? O método nunca deve ser substituído por subclasss: é usado para determinar a identidade do object (isto é, a.equal?(b) se a é o mesmo object que b ).

Esta é efetivamente comparação ponteiro.

Eu amo jtbandes responder, mas desde que é bastante longo, vou adicionar minha própria resposta compacta:

== , === , eql? equal?
são 4 comparadores, ou seja. 4 maneiras de comparar 2 objects, em Ruby.
Como, em Ruby, todos os comparadores (e a maioria dos operadores) são, na verdade, chamadas de método, você pode alterar, sobrescrever e definir a semântica desses methods de comparação. No entanto, é importante entender, quando as construções de linguagem interna do Ruby usam qual comparador:

== (comparação de valor)
Ruby usa: == em todos os lugares para comparar os valores de 2 objects, 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 hash key)
Ruby usa: eql? (em combinação com o método hash) para comparar chaves Hash. Na maioria das classs: eql? é idêntico a: ==.
Conhecimento sobre: ​​eql? só é importante quando você deseja criar suas próprias classs 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 class Ruby comumente usado também depende da comparação de chaves de hash.

equal? (comparação de identidade de object)
Ruby usa: igual? para verificar se dois objects são idênticos. Este método (da class BasicObject) não deve ser sobrescrito.

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

Operadores de igualdade: == e! =

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

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

O operador! =, Desigualdade de AKA ou bang-til, é o oposto de ==. Ele retornará verdadeiro se ambos os objects não forem iguais e falsos 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, as versões maiúsculas e minúsculas da mesma letra não são iguais e assim por diante.

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

 2 == 2.0 # Output: => true 

igual?

Ao contrário do operador ==, que testa se ambos os operandos são iguais, o método igual verifica se os dois operandos se referem ao mesmo object. 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 strings com o mesmo valor. No entanto, eles são dois objects distintos, com diferentes IDs de object. Portanto, o igual? o método retornará falso.

Vamos tentar novamente, só que desta vez b será uma referência a um. Observe que o ID do object é o mesmo para ambas as variables, pois elas apontam para o mesmo object.

 a = "zen" b = a a.object_id # Output: => 18637360 b.object_id # Output: => 18637360 a.equal? b # Output: => true 

eql?

Na class Hash, o eql? método é usado para testar as chaves para igualdade. Alguns antecedentes são necessários para explicar isso. No contexto geral da computação, uma function hash usa uma string (ou um arquivo) de qualquer tamanho e gera uma string ou um inteiro de tamanho fixo chamado hashcode, comumente chamado apenas de hash. Alguns tipos de códigos de hash comumente usados ​​são MD5, SHA-1 e CRC. Eles são usados ​​em algoritmos de criptografia, indexação de database, 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 collections tipo dictionary que armazenam dados em pares, consistindo de chaves exclusivas e seus valores correspondentes. Sob o capô, essas chaves são armazenadas como hashcodes. Tabelas de hash são comumente chamadas de hashes. Observe como a palavra hash pode se referir a um código hash ou a uma tabela de hash. No contexto da programação Ruby, a palavra hash quase sempre se refere à coleção de dictionary.

Ruby fornece um método interno chamado hash para gerar hashcodes. No exemplo abaixo, é preciso uma string e retorna um hashcode. Observe como as strings com o mesmo valor sempre possuem o mesmo hashcode, mesmo que sejam objects distintos (com diferentes IDs de object).

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

O método hash é implementado no módulo Kernel, incluído na class Object, que é a raiz padrão de todos os objects Ruby. Algumas classs, como Symbol e Integer, 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 

Em Ruby, quando armazenamos algo em um hash (coleção), o object fornecido como uma chave (por exemplo, string ou símbolo) é convertido e armazenado como um hashcode. Mais tarde, ao recuperar um elemento do hash (coleção), fornecemos um object como uma chave, que é convertida em um código 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? método se comporta de maneira semelhante ao método ==. No entanto, existem algumas exceções. Por exemplo, eql? não executa conversão de tipo implícito ao comparar um inteiro com um float.

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

Operador de igualdade de casos: ===

Muitas das classs internas do Ruby, como String, Range e Regexp, fornecem suas próprias implementações do operador ===, também conhecidas como case-equality, triple equals ou threequals. Como ele é implementado de maneira diferente em cada class, ele se comportará de maneira diferente dependendo do tipo de object em que foi chamado. Geralmente, retorna true se o object à direita “pertence a” ou “é um membro de” o object à esquerda. Por exemplo, ele pode ser usado para testar se um object é uma instância de uma class (ou uma de suas subclasss).

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

O mesmo resultado pode ser obtido com outros methods 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 class Fixnum, que é uma subclass da class Integer. O ===, is_a? e instance_of? methods retornam true se o object for uma instância da class dada ou de qualquer subclass. O método instance_of é mais estrito e só retorna true se o object for uma instância dessa class exata, não uma subclass.

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

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

Implementação de faixa de ===

Quando o operador === é chamado em um object 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 === invoca o método === do object da esquerda. Então (1..4) === 3 é equivalente a (1..4). === 3. Em outras palavras, a class do operando à esquerda definirá qual implementação do método === será chamadas, então as posições dos operandos não são intercambiáveis.

Regexp Implementação de ===

Retorna true se a string à direita corresponder à expressão regular à esquerda. / zen / === “practice zazen today” # Saída: => true # é o mesmo que “practice zazen today” = ~ / zen /

Uso implícito do operador === no caso / quando instruções

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 implicitamente usado o operador double equal (==), o intervalo 10..20 não seria considerado igual a um inteiro como 15. Eles correspondem porque o operador de igualdade tripla (===) é implicitamente usado em todas as declarações de caso / quando. 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 combinar strings e símbolos com padrões de regex.

A implementação do método = ~ nas classs String e Symbol espera uma expressão regular (uma instância da class Regexp) como um 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 class Regexp espera uma string ou um símbolo como um argumento.

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

Em todas as implementações, quando a string ou símbolo corresponde ao padrão Regexp, ele retorna um inteiro que é a posição (índice) da correspondência. Se não houver correspondência, ele retornará nulo. Lembre-se que, em Ruby, qualquer valor inteiro é “truthy” e nil é “falsy”, então o operador = ~ pode ser usado em declarações if e 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 menores. 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 .

=== # — igualdade de casos

== # — igualdade genérica

ambos funcionam parecidos, mas “===” até fazem declarações de caso

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

aqui a diferença

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

Ruby expõe vários methods diferentes para lidar com igualdade:

a.equal? ​​(b) # identidade do object – a e b referem-se ao mesmo object

a.eql? (b) # equivalência entre objects – a e b têm o mesmo valor

a == b # equivalência de object – a e b têm o mesmo valor com conversão de tipo.

Continue lendo clicando no link abaixo, isso me deu uma compreensão clara e resumida.

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

Espero que ajude os outros.

Eu gostaria de expandir o operador === .

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

Não.

Vamos entender realmente esse ponto.

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

Então, o que faz === fazer?

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

  • === corresponde a expressões regulares
  • === verifica a associação do intervalo
  • === verifica sendo instância de uma class
  • === chama expressões lambda
  • === às vezes verifica a igualdade, mas na maioria das vezes não

Então, como essa loucura faz sentido?

  • Enumerable#grep usa === internamente
  • case when declarações usam === internamente
  • Curiosidade, rescue usa internamente internamente

É por isso que você pode usar expressões regulares e classs e intervalos e até mesmo expressões lambda em um case when instruçã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 funcionam com o pattern === value também, assim como com o 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] 

Eu escrevi um teste simples para todos os itens 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)