Fork me on GitHub

Keep Learning Conhecimento nunca é o bastante

Postado em
6 December 2007 @ 11:06

Tag(s)
Artigos, Ruby

Tradução de artigo – Aquecimento Ruby: "De onde veio esse método?"

Russ Olsen iniciou uma série de artigos sobre Ruby em seu blog Technology As If People Mattered. Russ é o autor do livro Design Patterns in Ruby, já em pré-venda, à ser lançado em breve pela Addison-Wesley.

Conversei com Russ e pedi autorização para traduzir seus artigos, pois acredito que o conteúdo é muito bom e merece ser compartilhado no comunidade brasileira. Ele foi muito receptivo e autorizou sem problemas. Então, vamos ao artigo…

Aquecimento Ruby: “De onde veio esse método?”

Como você pode aprender uma nova linguagem de programação? Vai à aulas? Compra um livro? Apenas começa a escrever algum código? Todas essas coisas vão te ajudar – especialmente a última – mas se você realmente quer saber como os experts fazem isso, você tem que ler algum código. Não há nada como pegar código bem escrito e tentar descobrir como tudo funciona para te colocar no espírito de uma nova linguagem.

Se você está tentando aprender Ruby lendo código, tenho uma notícia boa e uma ruim. A boa é que Ruby foi concebida para ser facilmente lida por humanos. Ruby é tão flexível que você pode dizer muito com pouquíssimo código e isso, geralmente, torna a leitura de um programa Ruby um prazer. A ruim é que Ruby é tão flexível que iniciantes geralmente têm dificuldades se encontrando dentro de aplicações reais. Em particular, a natureza flexível do Ruby siginifica que o código que é executado quando você diz objeto.metodo nem sempre está onde você pensa.

Então, com o intuito de divulgar a mais pessoas minha linguagem favorita, vou guiá-los através de algumas maneiras que você pode usar para associar um nome de método em um objeto a um pedaço de código Ruby em particular. Como este é um tópico um pouco complexo, dividi a discussão em um par de artigos: este irá cobrir os meios mais trabalhosos pelos quais um programa pode conectar código Ruby a um nome de método, enquanto o próximo irá mergulhar em técnicas mais exóticas.

Aqui vamos nós…

Os suspeitos usuais: Classes e superclasses

Vamos começar com o básico do básico: todo objeto Ruby tem uma classe e uma superclasse e uma super-ultra-classe e assim em diante até chegar a Object, que está sozinha no topo da pilha. E para a surpresa de ninguém, quando o Ruby sai procurando por um método, ele começa com a classe e então sobe a árvore de classe para superclasse até que encontre o método ou não haja mais classes.

Por exemplo, posso estar modelando um carro:

class Car
  def color
    "Boring blue"
  end
 
  def number_wheels
    4
  end
end
 
class SportsCar < Car
  def color
    "Sizzling red"
  end
end
 
my_car = SportsCar.new

O que temos no código acima é uma classe SportsCar que é uma subclasse de Car que é, por padrão, uma subclasse de Object.

Chame um método em uma instância de SportsCar e você terá o que espera: my_car.color retornará um vermelho bem vivo (“Sizzling red”). No fim das contas, o método color é definido bem ali na classe SportsCar. De forma similar, se você chamar my_car.number_wheels, você descobrirá que não importa quão legal é seu novo brinquedo, ele sempre terá as mesmas quatro rodas. Graças à mágica da herança, Ruby descrobrirá o método number_wheels na classe Car depois de não encontrá-la em SportsCar.

Você pode também chamar to_s na sua instância de carro esportivo e ter um resultado como este:

#<sportsCar:0xb7d073b8>

O método to_s é entregue a você como cortesia da superclasse de Car, Object.

Finalmente, se você chamar um método não existente, como my_car.foobar, você verá uma mensagem de erro afirmando que não existe tal método: Ruby subiu toda a pilha de classes e reclamou quando chegou ao topo sem encontrar nada.

Então se você está lendo código e vê my_car.number_wheels, comece com o óbvio: procure o método number_wheels em SportsCar, depois em Car e assim em diante.

O módulo por trás das cortinas

Uma vez passadas as classes, as coisas ficam mais interessantes. Além do padrão de classes e
superclasses, Ruby também possui módulos. Um módulo é parecido com uma classe e, de fato, você os define de forma bem parecida:

module Convertible
  def top_down
    "Putting the top down"
  end
 
  def top_up
    "Putting the top up"
  end
end

Módulos podem parecer classes, mas você não pode instanciar um módulo. O que você pode fazer com um módulo é incluí-lo em sua classe:

class SportsCar < Car
  include Convertible
 
  def color
    "Sizzling red"
  end
end

O efeito de incluir um módulo em uma classe é inserir o módulo na cadeia de herança entre a classe e sua superclasse. Então se nós chamarmos top_down em uma instância de nossa nova classe SportsCar acima, Ruby procuraria pelo método primeiro na classe SportsCar (não está aqui) e então no módulo Convertible (aqui!). Se nós chamarmos o método color, Ruby procuraria em SportsCar (sem sorte), então em Convertible (aqui também não) e então em Car (ah ha!).

O mais legal dos módulos é que enquanto uma classe pode ter apenas uma superclasse, ela pode incluir quantos módulos quiser:

module NavigationSystem
  def location
    "39 53 N     75 15 W"
  end
end
 
class SportsCar < Car
  include Convertible
  include NavigationSystem
 
  def color
    "Sizzling red"
  end
end

A classe SportsCar agora inclui os módulos Convertible e NavigationSystem, então saberemos exatamente onde estamos quando baixarmos a capota. O fato de que os módulos são inseridos na hierarquia de classes bem acima da classe que os inclui tem algumas implicações interessantes.

Em primeiro lugar, como um módulo age um pouco como uma superclasse, um módulo não pode
sobrescrever um método da classe que o inclui. Por exemplo, imagine que definimos o método location diretamente na classe SportsCar e também incluímos o módulo NavigationSystem:

class SportsCar < Car
  include Convertible
  include NavigationSystem
 
  # Stuff deleted ...
 
  def location
    "Right here"
  end
end

Com o código acima, se você chamar o método location em uma instância de SportsCar, você sempre receberá “Right here” – o método definido na classe sempre vence.

Segunda, não é somente a sua classe que pode ter módulos: a superclasse e as classes acima também podem. Mas tudo funciona da maneira que você espera: quando Ruby começa a procurar por um método, primeiro ele examina a classe, depois em todos os módulos incluídos na mesma, então na superclasse e então em todos os seus módulos e assim em diante.

Terceira, se você incluir vários módulos em uma classe, os módulos serão colocados na hierarquia de classes de um modo que o último modo incluído é o primeiro a ser buscado. Isso significa que quando você chama um método numa instância de SportsCar, Ruby vai procurar primeiro na classe, depois no módulo NavigationSystem e depois no módulo Convertible – nessa ordem. Geralmente a ordem que você inclui módulos não faz muita diferença, mas se dois módulos definirem o mesmo método, é o método do último módulo que você incluiu que será chamado.

Então se você está tentando entender o código de uma aplicação e não consegue encontrar um método em nenhuma classe, procure nos módulos incluídos nessas classes.

Métodos Singleton: O estranho misterioso

Mas e se o método não está em nenhum lugar, nem mesmo nos módulos? Bem, ele pode estar
completamente sozinho. Veja, Ruby permite que você defina métodos singleton (nada a ver com o design pattern de mesmo nome). Um método singleton é um método que você define em um objeto, independentemente de sua classe ou de módulos.

Já ensinei Ruby a vários programadores Java experientes e, geralmente, a reação inicial deles à
métodos singleton varia do bom e velho “Isso simplesmente não é o correto” até náuseas. Mas, uma vez que seus ouvidos parem de doer, conclui-se que a possibilidade de customizar um único objeto sem ter que se preocupar sobre o que o resto da classe está fazendo é muito útil. Pense em todos aqueles casos que você já enfrentou em sua vida de programação, aqueles que quase se encaixam num método de classe, os equivalentes na programação ao cão de três pernas ou ao carro voador. Métodos singleton são feitos para esses casos.

Há algumas maneiras diferentes de encaixar um método singleton em um objeto. Por exemplo, se você quiser instalar um som personalizado, único, em seu carro, você poderia usar a seguinte notação:

my_car = SportsCar.newclass << my_car
 
  def custom_sound_system
    "... BYYYYING A STAIIIIRWAY to heaVennnnnnn"
  end
 
end

Alternativamente, você pode melhorar o seu som simplesmente definindo o método diretamente em seu objeto:

my_car = SportsCar.new
 
def my_car.custom_sound_system
  "... BYYYYING A STAIIIIRWAY to heaVennnnnnn"
end

As duas maneiras resultarão em um objeto my_car que tem um método diferente de todos os outros objetos da classe SportsCar.

E como tudo isso funciona? Muito simples: cada objeto tem um tipo de “classe invisível” entre o
próprio objeto e sua classe regular. Quando você define um método singleton em um objeto, o método vai diretamente para essa “classe singleton”. A classe singleton é uma classe real e, como qualquer outra classe, pode incluir módulos. Então, uma maneira de personalizar seu carro seria adicionar um novo sistema de exaustão:

module CoolExhaust
  def rev_engine
    "VAROOOOOOOOOOOOM"
  end
 
  def monthly_disturbing_the_peace_fines
    279
  end
end
 
my_car = SportsCar.new
 
class << my_car
  include CoolExhaust
end

Seu carro, e somente seu carro, agora tem um novo sistema de exaustão: agora você pode estourar seu motor pela manhã e passar toda a tarde no tribunal.

A classe singleton se parece um pouco com um módulo: enquanto um módulo fica entre a classe e sua super classe, a classe singleton fica entre o objeto e sua classe. Quando Ruby começa a procurar por um método é a classe singleton o primeiro lugar que é vasculhado, antes mesmo da classe regular do objeto, dos módulos e, certamente, qualquer superclasse.

Então, para resumir nossas aventuras até agora, descobrimos que cada instância de objeto no Ruby tem uma classe singleton que é única àquele objeto. Quando Ruby inicia a busca por um método em um objeto, ele consulta a classe singleton (e seus módulos) primeiro. Se o método não for encontrado, Ruby buscará na classe regular e seus módulos. Se ainda não for encontrado nada, Ruby procederá para a superclasse, fazendo a mesma busca na classe e módulos. É claro que se o método não for encontrado em lugar algum, será lançado um erro… Bem, não exatamente. O que Ruby faz quando alguém chama um método inexistente é um assunto muito interessante, mas um assunto que vou deixar para o próximo artigo.

Russ Olsen

Acesse aqui o artigo original.

Em breve traduzirei a segunda parte.


4 Comentários

Comentário por
Lauro
6 December 2007 @ 13:12

Muito boa a dica, o artigo do cara ta melhor que a intro dele a linguagem ruby no capítulo 2 do livro que está em beta, o restante do livro que ainda estou lendo no safaribooks é muito bom.

Na tua tradução creio que a primeira linha faltou colocar “pode”
Ficando assim:
Como você pode aprender uma nova linguagem de programação?

No mais ta perfect


Comentário por
lucashungaro
6 December 2007 @ 13:39

Opa, valeu Lauro, “engoli” uma palavra ali mesmo. 😉


Comentário por
Douglas
6 December 2007 @ 12:51

Ótimo post!


Comentário por
Marcelo Madeira
8 December 2007 @ 13:02

Excelente post.
Ficarei aguardando ancioso a segunda parte.

abraços
Marcelo


Deixe um comentário