and - relational operators ruby




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

=== # --- 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

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


Operadores de igualdade: == e! =

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

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

O operador! =, Desigualdade AKA ou bang-til, é o oposto de ==. Ele retornará verdadeiro se ambos os objetos 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 tipos diferentes (por exemplo, integer e float), se o valor numérico for o mesmo, == retornará true.

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 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 strings com o mesmo valor. No entanto, eles são dois objetos distintos, com diferentes IDs de objeto. Portanto, o igual? o método retornará falso.

Vamos tentar novamente, só que desta vez b será uma referência para a. Observe que o ID do objeto é o mesmo para ambas as 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 as chaves para igualdade. Alguns antecedentes são necessários para explicar isso. No contexto geral da computação, uma função 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 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 tipo dicionário 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 dicionário.

Ruby fornece um método interno chamado hash para gerar hashcodes. No exemplo abaixo, leva uma string e retorna um hashcode. Observe como as strings com o mesmo valor sempre possuem o mesmo hashcode, mesmo que sejam objetos distintos (com IDs de objetos 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 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 objeto 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 objeto 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 caso: ===

Muitas das classes 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 classe, ele se comportará de maneira diferente dependendo do tipo de objeto em que foi chamado. Geralmente, ele retorna true se o objeto à direita "pertence a" ou "é um membro de" o objeto à esquerda. Por exemplo, 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 obtido com outros métodos que provavelmente são mais adequados para o trabalho. Geralmente, é melhor escrever 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? métodos retornam true se o objeto for uma instância da classe dada ou de qualquer subclasse. O método instance_of é mais estrito e só retorna true se o objeto for uma instância dessa classe exata, não uma subclasse.

O is_a? e kind_of? Os 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 faixa de ===

Quando o operador === é chamado em um objeto de intervalo, ele retorna verdadeiro 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 objeto da esquerda. Então (1..4) === 3 é equivalente a (1..4). === 3. Em outras palavras, a classe 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.

Implementação Regexp de ===

Retorna true se a string à direita corresponder à expressão regular à esquerda. / zen / === "praticar zazen hoje" # 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ô em 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 classes String e Symbol espera uma expressão regular (uma instância da classe 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 classe 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 é "verdade" e nil é "false", 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 .


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)

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ções abrangem a associação
  • === verifica sendo instância de uma classe
  • === 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, o rescue usa === internamente

É por isso que você pode usar expressões regulares e classes 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]

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

Nota lateral: se você quiser experimentar esses objetos 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 objeto. Normalmente, esse método é substituído em classes descendentes para fornecer um significado específico da classe.

Esta é a comparação mais comum e, portanto, o lugar mais fundamental em que 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 normalmente substituído por descendentes para fornecer semântica significativa em instruções case.

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 de como case + Regex pode tornar o código muito mais limpo. E, claro, fornecendo sua própria implementação === , você pode obter semânticas de case customizados.

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 objetos da classe Object , eql? é sinônimo de == . As subclasses 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 substituir isso para seus próprios usos, ou você pode sobrescrever == e usar o alias :eql? :== alias :eql? :== então os dois métodos 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 subclasses: ele é usado para determinar a identidade do objeto (isto é, a.equal?(b) iff a é o mesmo objeto que b ).

Esta é efetivamente comparação ponteiro.





equality