Vamos falar um pouco sobre comparação de objetos em Ruby
Nota: no momento de escrita deste artigo, o Ruby está na versão 2.6.3, então essa foi a documentação utilizada como referência.
Como em qualquer linguagem de programação orientada a objetos, Ruby possui formas de comparar objetos. Para verificar igualdade, por exemplo, podemos usar os métodos ==
, eql?
, equal?
ou até ===
. Sim, todos são métodos da classe Object
e, por isso, podem ser sobrescritos no seu código para executar ações especializadas nas suas próprias classes. No entanto, vamos começar do início, falando um pouco sobre como funcionam esses métodos.
O ===
é essencialmente igual ao ==
, mas é utilizado implicitamente em cenários case/when
e costuma ser sobrescrito especificamente nesses casos para obter resultados mais precisos. Por isso, não vamos discorrer muito sobre ele.
Vamos demonstrar o comportamento dos demais métodos com um pouco de código:
class MinhaClasse; end
class OutraClasse; end
a = b = MinhaClasse.new
c = d = OutraClasse.new
a == b # => true
a == c # => false
c == d # => true
a.eql? b # => true
a.eql? c # => false
c.eql? d # => true
a.equal? b # => true
a.equal? c # => false
c.equal? d # => true
Quando duas variáveis fazem referência ao mesmo objeto, chamar qualquer um dos métodos retorna verdadeiro. No entanto, quando as variáveis referenciam objetos com ids
diferentes, mesmo que seus parâmetros sejam idênticos, os métodos vão nos dizer que são objetos diferentes. Então, qual é a vantagem de termos tantos métodos que fazem a mesma coisa?
Vamos fazer mais dois testes com strings e com números:
str1 = 'Hello World'
str2 = 'Hello World'
str1 == str2 #=> true
str1.eql? str2 #=> true
str1.equal? str2 #=> false
value1 = 1
value2 = 1.0
value1 == value2 #=> true
value1.eql? value2 #=> false
value1.equal? value2 #=> false
Aqui já podemos notar diferenças. Comparando strings, eql?
e ==
se comportam da mesma maneira. No entanto, quando comparamos um inteiro com um número decimal, eql?
retorna falso, enquanto ==
, verdadeiro. Isso acontece porque ==
executa conversão de tipos numéricos quando invocado, mas o eql?
não. Sendo assim, 1 == 1.0 #=> true
, mas 1.eql? 1.0 #=> false
, afinal inteiro é diferente de decimal.
Já o equal?
retorna falso nos testes com strings e com números, porque esse método verifica se as variáveis referenciam o mesmo objeto, ou seja, ele checa se possuem o mesmo id
, mas não compara os valores atribuídos. Por isso, não é recomendado que o equal?
seja sobrescrito. Os demais métodos, no entanto, foram desenhados de forma que possam ser sobrescritos para implementar especializações da classe Object
.
Agora que entendemos as diferenças entre os métodos, vamos ver como implementar igualdade para fazer suas classes mais eficazes. No exemplo abaixo, criamos uma classe Conta
que se refere a uma conta bancária e possui os parâmetros numero
, nome
e saldo
.
class Conta
attr_accessor :numero, :nome, :saldo
def initialize(numero, nome, saldo)
@numero = numero
@nome = nome
@saldo = saldo
end
Se criarmos duas contas com os mesmos dados e verificarmos igualdade com ==
:
conta1 = Conta.new(1234, "Paulo Cardoso", 10)
conta2 = Conta.new(1234, "Paulo Cardoso", 10)
conta1 == conta2
#=> false
Se quisermos usar esse método para que retorne verdadeiro caso os parâmetros dos nossos objetos Conta
sejam idênticos, podemos sobrescrever o método ==
para que ele compare os parâmetros da classe, no lugar do id
dos objetos.
class Conta
attr_accessor :numero, :nome, :saldo
def initialize(numero, nome, saldo)
@numero = numero
@nome = nome
@saldo = saldo
end
def == (other)
instance_variables == other.instance_variables
end
end
conta1 = Conta.new(1234, "Paulo Cardoso", 100)
conta2 = Conta.new(1234, "Paulo Cardoso", 100)
conta1 == conta2
#=> true
Pronto!
Neste artigo entendemos melhor como funcionam os métodos que avaliam igualdade em Ruby e vimos como fazer classes mais eficazes, implementando o método ==
.
TL;DR
eql?
se comporta igual a==
, exceto em comparações de números, poiseql?
não faz conversões de tipos numéricos.===
se comporta igual a==
, mas é normalmente sobrescrito para ser utilizado emcase
.equal?
verifica se as variáveis referenciam o mesmo objeto, ou seja, ele checa se possuem o mesmoid
.