Metaprogramação com Ruby: Como mudar o comportamento da linguagem

em Artigos, Ruby.

Lembro que sempre que falava com alguém sobre Ruby a conversa era mais ou menos assim:

– Cara, Ruby é muito foda, é uma linguagem totalmente orientada à objetos.
– Bro, tem infinitas linguagens orientadas à objetos…
– Não, você não tá ligado, em Ruby TUDO é objeto, você consegue sobrescrever qualquer coisa.
– Tá, em Java tem override, eu consigo sobrescrever coisas, o que é que tem demais nisso? – Não cara, em Ruby tudo MESMO é um objeto, números são objetos, eu consigo fazer o sinal de + virar – !
– E alguém iria querer fazer o + virar – ? Como assim números são objetos? Números são números, para de falar besteira bro…

E aí ficava por isso mesmo. E o engraçado é que sempre que vejo alguém falar sobre como Ruby é legal, acaba saindo esse “consigo fazer o sinal de + virar – !”. E é claro, ninguém vê sentido ou utilidade nisso. Vou tentar mostrar então o que isso significa e que sim, isso é bem legal!

Como assim tudo é objeto e pode ser manipulado?

Um número em Ruby é um objeto, ou seja, possui uma classe como qualquer outro objeto no nosso código. Podemos ver isso com:

2.class

Isso retornará:

Fixnum

Em Ruby, core classes, que são classes bases da linguagem podem ser alteradas como qualquer outra de forma simples. E, o + não tem nada de especial, é apenas um método da classe, logo, o exemplo que tanto falam pode ser feito da seguinte maneira:

class Fixnum
  def +(value)
    self - value
  end
end
 
puts 5 + 1

Tal código escreverá 4, pois agora o + significa menos. Para esclarecer rapidamente, basta entender que o mesmo código acima pode ser escrito como:

5.+(1)

Ou seja, + é apenas um método do objeto 5 que está recebendo o parâmetro 1.

Para ilustrar um pouco mais, poderíamos fazer algo como:

class Fixnum
  def mais(value)
    self + value
  end
end
 
cinco = 5
um    = 1
 
puts cinco.mais(um)
 
puts cinco + um

Tal código retornará:

6
6

Percebe como tudo é objeto e por conta disto pode ser facilmente manipulado em Ruby? Vamos dar um exemplo de como isso poderia se tornar algo útil e interessante.
Sabemos que uma semana tem 7 dias, se queremos somar semanas para ver quantos dias temos, podemos fazer os números responderem isso. Confuso? Vejamos:

class Fixnum
  def semana
    self * 7
  end
 
  def semanas
    self * 7
  end
end
 
puts 2.semanas
puts 1.semana
puts 2.semanas + 1.semana

O resultado:

14
7
21

Interessante, não? Isso é a base do que acontece quando utilizamos Ruby on Rails e passamos a contar com certas comodidades ao poder utilizar coisas como 2.weeks, por exemplo.

E se podemos manipular números, podemos fazer o mesmo com qualquer coisa, inclusive com textos. Vamos alterar a classe String e decidir que agora todo texto que chamar o método come_o_cogumelo vai ficar com suas letras em maiúsculo:

class String
  def come_o_cogumelo
    self.upcase
  end
end
 
puts "mario".come_o_cogumelo

Isso irá retornar:

MARIO

Tudo é objeto, tudo é manipulável.

Como fazer Métodos “Inteligentes”

Outra coisa que existe em Ruby também é um tal de method_missing, com ele você consegue interceptar em uma classe todo método que é chamado e não existe, um exemplo do que é possível fazer:

joao = Pessoa.new
 
joao.bolso < 50.dinheiros
 
joao.bolso < 10.dinheiros
 
joao.bolso - 20.dinheiros
 
puts joao.tem_dinheiro_ai_bro? # tenho 40 reais cara...
 
puts joao.me_da_20_reais! # blz, pega 20 reais...
 
puts joao.quero_mais_30_reais_emprestados! # blz, pega 30 reais...
 
puts joao.sobrou_quanto_de_grana? # tenho -10 reais cara...

Sim, conseguimos fazer um objeto se comportar exatamente dessa forma. Manipulamos a classe Fixnum para existir o método dinheiros, criamos o método < na classe Pessoa para “jogar” dinheiro dentro do bolso dela e com o método method_missing na classe Pessoa, capturamos todas as perguntas e afirmações feitas que não existem de fato na classe, as identificamos e interpretamos utilizando Expressões Regulares (você pode ler mais sobre elas aqui) e fazemos o que acreditamos que o usuários quis dizer com aquilo:

class Fixnum
  def dinheiros
    self
  end
end
 
class Pessoa
  def initialize
    @reais = 0
  end
 
  def bolso
    self
  end
 
  def &amp;amp;amp;lt;(reais)
    @reais += reais
  end
 
  def -(reais)
    @reais -= reais
  end
 
  def method_missing(method_name, params = nil)
    if method_name =~ /[grana|dinheiro].*\?/
      puts "tenho #{@reais} reais cara..."
    elsif method_name =~ /[da|quero].*\d.*\!/
      @dinheiro_emprestado = method_name.to_s.scan(/\d{1,}/).first.to_i
      @reais -= @dinheiro_emprestado
      puts "blz, pega #{@dinheiro_emprestado} reais..."
    end
  end
end

Você pode ver o código sendo executado em: http://ruby.gbaptista.com/p/jeyI4A 

Tudo bem, o código ficou bem ruim e feio e, além disso, despejei infinitos conceitos complexos, todos de uma vez para você. Com calma é possível pesquisar e estudar cada detalhe… A ideia foi mostrar como você realmente consegue manipular tudo em Ruby, criando qualquer coisa.

Somando Cores

Um outro exemplo um pouco mais simples, e se quiséssemos somar cores primárias para ver o resultado? Exemplo: Azul + Amarelo = Verde.

Podemos manipular a classe String:

class String
  alias :original_sum :+
 
  COLORS = {
    'azul+amarelo'     =&amp;amp;gt; 'verde',
    'amarelo+azul'     =&amp;amp;gt; 'verde',
    'vermelho+amarelo' =&amp;amp;gt; 'laranja',
    'amarelo+vermelho' =&amp;amp;gt; 'laranja',
    'azul+vermelho'    =&amp;amp;gt; 'violeta',
    'vermelho+azul'    =&amp;amp;gt; 'violeta'
  }
 
  def +(value)
    new_color = COLORS["#{self.downcase}+#{value.downcase}"]
 
    if new_color
      new_color
    else
      self.original_sum(value)
    end
  end
end

E agora, basta somar as cores:

puts "Azul" + "Amarelo"
 
puts "Amarelo" + "Vermelho"
 
puts "Vermelho" + "Azul"
 
puts "lorem " + "ipsum"

O resultado:

verde
laranja
violeta
lorem ipsum

E assim temos infinitas possibilidades por conta da flexibilidade da linguagem.

O Dark Side da Mágica

Nessa vida, nem tudo são flores. Essa possibilidade de manipular qualquer coisa de forma tão simples abre um leque de infinitas possibilidades ao trabalhar com a linguagem… Porém, tal possibilidade abre uma porta que pode nos levar à caminhos perigosos como por exemplo exagerar na utilização de Monkey PatchO Pablo tem um post bem legal que explica o que eles são e como utilizá-los de forma consciente, dá uma olhada lá! > Monkey Patch – Sim, não, quando?

E falando em Ruby, nos dias 23 e 24 de Setembro, acontece na Fecomercio em São Paulo, o RubyConf Brasil 2016! Serão 2 dias de evento, com mais de 80 palestrantes e com 5 trilhas sobre diversos assuntos além de Ruby, para você que deseja aprender e atualizar seus conhecimentos sobre Ruby on Rails, Devops, JavaScript, Elixir, Clojure, Go, Rust, Crystal e outras linguagens de programação. Não fique de fora do maior evento de Ruby da América Latina, inscreva-se aqui no RubyConf Brasil!