Operadores em Ruby e seus métodos

Leia em: 11 minutos

Ruby esta se tornando uma línguagem popularmente conhecida na web. Neste post tentarei mostrar a fundo o que são os operadores no Ruby, e por que eles são tão diferentes de qualquer coisa que já viu antes.

Operadores

O exemplo abaixo mostra dois operadores familiares.

1 + 2 # 3

person[:name] = "Joe"

Nas expressões acima denominamos dois operadores, um com o sinal de mais (operador aritmético) e outro com um sinal de igual (operador de atribuição). Essa maneira não é diferente do que você já esta acostumado a utilizar em outras liguagens como: Javascript, Java, C#. Mas no Ruby os operadores são realmente chamados de métodos.

Vamos tentar executar o seguinte comando no ruby:

1.+(2)

# 3

Na execução, o operador +, fez uma chamada de método no objeto 1, passando o objeto 2 de parâmetro.

name = 'Saulo'

name.+(' Santiago') # O retorno será "Saulo Santiago", mas a variável `name` continua armazenando 'Saulo'

name += " Santiago" # Agora a variável `name` armazena 'Saulo Santiago'

Ficou claro que podemos fazer concatenação com o método +. Como bônus o Ruby define o operador += com base no operador + (note que você não pode utilizar o += como um método), sendo assim conseguimos uma sintaxe mais enxuta.

Isso nos dá um poder incrível. Nós podemos personalizar o significado de adição, subtração e atribuir objetos em nossas classes personalizadas.

Vamos tomar um passo adiante.

Construindo nossos próprios métodos de operação

Vamos tentar estabelecer e criar um método.

Para este exemplo, vamos pensar em um objeto chamado Geladeira, que pode adicionar as coisas via o operador + e retirar as coisas via o operador -. Vamos iniciar nossa classe:

class Fridge
  def initialize (beverages=[], foods=[])
    @beverages = beverages
    @foods = foods
  end

  def + (item)
  end

  def - (item)
  end
end

Nossa função do initialize é bem simples: nós tomamos dois parâmetros (ambos por padrão retornam matrizes vazias, se nada for enviado), que são atribuidos a variáveis ​​de instância.

def + (item)
  if item.is_a? Beverage
    @beverages.push item
  else
    @foods.push item
  end
end

Isso é muito simples. Cada objeto tem um método is_a? que leva um único parâmetro: a classe. Se o objeto é uma instância dessa classe, ele irá retornar true, caso contrário, retornará false.

Assim, este diz que se o item que estamos adicionando na Geladeira é uma bebida, nós vamos adicioná-lo à matriz @beverages. Caso contrário, nós vamos adicioná-lo à matriz @foods.

Agora, que tal levarmos as coisas para fora da geladeira? Este método ficará um pouco mais complexo.

def - (item)
  ret = @beverages.find do |beverage|
    beverage.name.downcase == item.downcase
  end

  return @beverages.delete ret unless ret.nil?

  ret = @foods.find do |food|
    food.name.downcase == item.downcase
  end

  @foods.delete ret
end

No código acima recebemos uma string de parâmetro, que representará o nome do objeto que queremos remover.

Usamos o método find para interar sobre a matriz e buscar um ou mais objetos que tenham o mesmo valor que o parâmetro que foi enviado.

Se houver algum item que corresponde na matriz, ele será armazenadas no variável ret, a seguir será removido com @beverages.delete ret e retornado, caso contrário, ret será nula.

Você deve estar pensando, por que usamos o return no meio do método? Se não estivessemos utilizando o return o código continuaria sendo executado, e neste caso não queriamos este comportamento.

Então para parar a execução de um bloco de código em um determinado lugar no método que não seja a última instrução, colocamos um return.

Caso nada seja retornado, isso significa que o item não foi encontrado no @beverages. Portanto, nós assumimos que ele está em @foods. Nós fizemos a mesma coisa para encontrar o item em @foods e em seguida, devolvemos.

Note que nas duas verificações estamos convertendo as strings todas para minúsculas, isso garante uma verificação mais segura.

Antes de testarmos isso, vamos precisar de duas classes: Food e Beverage:

class Beverage
  attr_accessor :name

  def initialize name
    @name = name
    @time = Time.now
  end
end

class Food
  attr_accessor :name

  def initialize name
    @name = name
    @time = Time.now
  end
end

Depois de ter criado as classes; vamos testa-ló no IRB.

2.2.2 :001 > f = Fridge.new

 => #<Fridge:0x007fa9d305d140 @beverages=[], @foods=[]>

2.2.2 :002 > f + Beverage.new("water")
 => [#<Beverage:0x007fa9d3054158 @name="water", @time=2015-07-20 01:01:04 -0300>]

2.2.2 :003 > f + Beverage.new("beer")
 => [#<Beverage:0x007fa9d3054158 @name="water", @time=2015-07-20 01:01:04 -0300>, #<Beverage:0x007fa9d3046aa8 @name="beer", @time=2015-07-20 01:01:18 -0300>]

2.2.2 :004 > f + Food.new("bread")
 => [#<Food:0x007fa9d303e1c8 @name="bread", @time=2015-07-20 01:01:27 -0300>]

2.2.2 :005 > f + Food.new("eggs")
 => [#<Food:0x007fa9d303e1c8 @name="bread", @time=2015-07-20 01:01:27 -0300>, #<Food:0x007fa9d3034ba0 @name="eggs", @time=2015-07-20 01:01:30 -0300>]

2.2.2 :006 > f
 => #<Fridge:0x007fa9d305d140 @beverages=[#<Beverage:0x007fa9d3054158 @name="water", @time=2015-07-20 01:01:04 -0300>, #<Beverage:0x007fa9d3046aa8 @name="beer", @time=2015-07-20 01:01:18 -0300>], @foods=[#<Food:0x007fa9d303e1c8 @name="bread", @time=2015-07-20 01:01:27 -0300>, #<Food:0x007fa9d3034ba0 @name="eggs", @time=2015-07-20 01:01:30 -0300>]>

2.2.2 :007 > f - 'water'
 => #<Beverage:0x007fa9d3054158 @name="water", @time=2015-07-20 01:01:04 -0300>

2.2.2 :008 > f
 => #<Fridge:0x007fa9d305d140 @beverages=[#<Beverage:0x007fa9d3046aa8 @name="beer", @time=2015-07-20 01:01:18 -0300>], @foods=[#<Food:0x007fa9d303e1c8 @name="bread", @time=2015-07-20 01:01:27 -0300>, #<Food:0x007fa9d3034ba0 @name="eggs", @time=2015-07-20 01:01:30 -0300>]>
2.2.2 :053 >

Como você observou, criei o objeto Fridge e adicionei nele várias classes de Beverage e Food, e no final retirei um objeto pelo nome (water).

Pronto, conseguimos personalizar os operadores de adição e subtração para nossa classe Fridge.

Operadores de Get e Set

Agora vamos escrever métodos para a definir e obter operadores utilizando os hashes. Mas antes iremos analisarmos o seguinte trecho de código:

person = {}

person[:name] = "Saulo"

Mas uma vez que os operadores são métodos, podemos fazê-lo desta maneira:

person.[]=(:age, 24) # to set

person.[](:name) # to get

Tudo isto funciona perfeitamente pois estes são métodos normais, tendo apenas uma sintaxe especial.

Vamos fazermos uma classe chamada Club. Nosso clube tem diferentes membros com diferentes funções. No entanto, a gente pode querer ter mais do que um membro com uma determinada função.

Então, o nosso exemplo irá ser composto por um hash de membros e seus papéis. Se atribuirmos um segundo membro a uma função, em vez de substituir o primeiro, iremos adicioná-lo.

class Club
  def initialize
    @members = {}
  end

  def [] (role)
    @members[role]
  end

  def []= (role, member)
  end
end

O método responsável por trazer um membro da matriz é simples. Mas o método para adicionar um novo membro é mais complexo:

def []= (role, member)
  if @members[role].nil?
    @members[role] = member
  elsif @members[role].is_a? String
    @members[role] = [ @members[role], member ]
  else
    @members[role].push member
  end
end

Se não houver membros com função definida, o membro é atribuido para uma nova função dentro do hash members.

Se a função esta definido como uma string, é feito uma conversão em uma nova matriz com os membros originais e o novo membro.

Finalmente, se nenhuma dessas opções forem verdadeiras, significa que a função já existe na matriz, e por isso, o novo membro é apenas adicionado.

Podemos testar isto da seguinte maneira:

c = Club.new

c[:developer] = "Saulo"

c[:engineer] = "Foo"

c[:engineer] = "Bar"

c[:developer] # "Saulo"

c[:engingeer] # [ "Foo", "Bar" ]

Listando todos os Operadores

Assumindo que a variável a seja 10 e a b seja 20. Segue a lista completa dos operadores:

Operadores aritiméticos

Operador O que faz? Exemplo Método?
+ Adiciona valores da esquerda com o da direita a + b irá retornar 30 Sim
- Subtrai os valores da esquerda com o da direita b - a irá retornar 10 Sim
* Multiplica os valores a * b irá retornar 200 Sim
/ Divide o valor da esquerda com o da direita b / a irá retornar 2 Sim
% Divide o valor da esquerda com o da direita e retorna a sobra da divisão b % a irá retornar 0 Sim

Operadores de comparações

Operador O que faz? Exemplo Método?
== Verifica se o valor dos dois operandos são iguais ou não, se sim, então a condição se torna verdadeira. a == b não é verdadeiro Sim
!= Verifica se o valor dos dois operandos são diferentes ou não, se sim, então a condição se torna verdadeira. a != b é verdadeiro Sim
> verifica se o valor da esquerda é maior que o da direita, se sim retorna verdadeiro a > b não é verdadeiro Sim
< verifica se o valor da esquerda é menor que o da direita, se sim retorna verdadeiro a > b é verdadeiro Sim
>= verifica se o valor da esquerda é maior ou igual ao da direita, se sim retorna verdadeiro a >= b não é verdadeiro Sim
<= verifica se o valor da esquerda é menor ou igual ao da direita, se sim retorna verdadeiro a <= b é verdadeiro Sim
<=> Operador de comparação combinada. Retorna 0 se primeiro operando é igual ao segundo, retorna 1 se o primeiro operando for maior que o segundo e retorna -1 se o primeiro operando for menor que o segundo. a <=> b retorna -1 Sim
=== Usado para testar a igualdade dentro de uma cláusula que contenha instruções. (1...10) === 5 é verdadeiro Sim
.eql? Verdadeiro se o receptor e o argumento têm o mesmo tipo e valor. 1 == 1.0 retorna verdadeiro, mas em 1.eql?(1.0) não é verdadeiro Sim
.equal? Verdadeiro se o receptor e o argumento tem a mesma identificação do objeto. se foo_obj é duplicado para bar_obj então foo_obj == bar_obj é verdade, a.equal? bar_obj é falso, mas a.equal? foo_obj é verdade. Sim

Operador de Set e Get

Assumimos que a variável y = {} sempre esta inicializada com um hash vazio.

Operador O que faz? Exemplo Método?
[]= Operador de Set y[]=(:red, '#ff0000') irá atribuir um hash que contém a chave red com o valor #ff0000 Sim
[] Operador de Get y[:red] irá retornar o valor da chave red Sim

Operadores binários

Imagine que que x = 18 e y = 20, em binário os valores são:

(x = 18).to_s(2)     #=> "10010"
(y = 20).to_s(2)     #=> "10100"
Operador O que faz? Exemplo Método?
& Operador AND (x & y).to_s(2) irá retornar "10000" Sim
pipe Operador OR (x pipe y).to_s(2) irá retornar "10110" Sim
^ Operador XOR (x ^ y).to_s(2) irá retornar "110" (zeros à esquerda são omitidos) Sim
~ Operador NOT (~x).to_s(2) irá retornar "-10011" Sim
<< Operador com desvio a esquerda (x << 2).to_s(2) irá retornar "1001000" Sim
>> Operador com desvio a direita (x >> 2).to_s(2) irá retornar "100" Sim

Operadores lógicos

Operador O que faz? Exemplo Método?
and ou && Se ambas condições forem verdadeiras, então é retornado true (a && b) é verdadeiro Não
or ou double pipe Se algumas das condições forem diferente de zero, o retorno é true (a or b) é verdadeiro Não
not ou ! Inverte o estado da lógica do operando !(a && b) é falso Não

Operadores de atribuições

Operador O que faz? Exemplo Método?
= Operador de atribuição simples, atribui valores da direita para o lado esquerdo c = a + b é atribuido o valor da soma de a + b em c Não
+= Adiciona e faz a atribuição c += a é equivalente a c = c + a Não
-= Subtrai e faz a atribuição c -= a é equivalente a c = c - a Não
*= Multiplica e faz a atribuição c *= a é equivalente a c = c * a Não
/= Divide e faz a atribuição c /= a é equivalente a c = c / a Não
%= Divide e faz a atribuição do resto c %= a é equivalente a c = c % a Não
**= Exponencia e faz a atribuição c **= a é equivalente a c = c ** a Não

Operador ternário

Operador O que faz? Exemplo Método?
? : Expressão condicional Se a condição é verdadeira? então o valor é x caso contrário o valor é y Não

Operadores de alcance

Operador O que faz? Exemplo Método?
.. Cria um alcance do ponto de partida até o ponto final inclusivo 1..10 cria um intervalo de 1 a 10 Não
... Cria um alcance do ponto de partida até o ponto final exclusivo 1..10 cria um intervalo de 1 a 9 Não

Nota: Operadores com um Sim na coluna Método?, na verdade, são métodos que podem serem subscritos.

Veja mais posts sobre:
ruby

Comentários:

Deixe sua dúvida, sugestão ou crítica, estou ansioso para saber tudo o que você achou sobre este post:
Saulo Santiago