Back-End

29 jun, 2012

Conheça 06 recursos incompreendidos do Ruby

Publicidade

Suponha que você seja um desenvolvedor C++ e precise criar alguns protótipos rapidamente no Ruby. Ao selecionar um livro de referência do Ruby, como o Pickaxe, ou navegar no site do Ruby, você vê construções familiares, como declaração de classe, suporte para encadeamentos e manipulação de exceção.

Justo quando pensava que sabia como o Ruby funcionava, você percebe que a simultaneidade em seu código Ruby não está se comportando como encadeamentos Boost, catch e throw não são o que parecem e outras pessoas usaram algo chamado self em todo o seu script do Ruby.

Se você for um programador C++ e precisar trabalhar no Ruby, necessitará de um pequeno aprendizado. Este artigo discute seis recursos do Ruby que são, geralmente, mal entendidas, especialmente se você vier de um ambiente semelhante, mas não muito, como o C++:

  1. A hierarquia de classes do Ruby
  2. Métodos singleton no Ruby
  3. A palavra-chave automaticamente
  4. O método method_missing
  5. Manipulação de exceção
  6. Passagem

Observação: Todo o código neste artigo foi testado e baseado no Ruby versão 1.8.7.

Bem-vindo ao Ruby!

Hierarquia de classes no Ruby

A hierarquia de classes no Ruby pode ser complicada. Crie uma classe do tipo Cat e comece a jogar com sua hierarquia:

Listagem 1. Hierarquia de Classes Implícita no Ruby

irb(main):092:0> class Cat
irb(main):093:1> end
=> nil

irb(main):087:0> c = Cat.new
=> #<Cat:0x2bacb68>
irb(main):088:0> c.class
=> Cat
irb(main):089:0> c.class.superclass
=> Object
irb(main):090:0> c.class.superclass.superclass
=> nil
irb(main):091:0> c.class.superclass.superclass.superclass
NoMethodError: undefined method `superclass' for nil:NilClass
from (irb):91
from :0

Todos os objetos no Ruby (até mesmo os objetos definidos pelo usuário) são descendentes da classe Object , que é obviamente da Listagem 1. Isso está em nítido contraste com o C++. Não há nada como um tipo de dados simples—por exemplo, C/C++ int ou double. A Listagem 2 mostra a hierarquia de classes para o número inteiro 1.

Listagem 2. Hierarquia de Classes para 1

irb(main):100:0> 1.class
=> Fixnum
irb(main):101:0> 1.class.superclass
=> Integer
irb(main):102:0> 1.class.superclass.superclass
=> Numeric
irb(main):103:0> 1.class.superclass.superclass.superclass
=> Object

Até aqui, tudo bem. Agora você sabe que classes em si são objetos do tipo Class. Class , por sua vez, é finalmente derivado de Object, conforme demonstrado na Listagem 3, com a classe String integrada do Ruby.

Listagem 3. Hierarquia de Classes para Classes

irb(main):100:0> String.class
=> Class
irb(main):101:0> String.class.superclass
=> Module
irb(main):102:0> String.class.superclass.superclass
=> Object

Módulo é a classe base para Class, e vem com o aviso de que não é possível instanciar objetos de Módulo definidos pelo usuário diretamente. Se não deseja entrar na parte interna do Ruby, é seguro considerar o Módulo como tendo características semelhantes a um namespace do C++ : É possível definir seus próprios métodos, constantes, e assim por diante. É possível incluir um Módulo em uma Classe e, voilà, todos os elementos do Módulo agora são magicamente os elementos da Classe. A listagem 4 dá um exemplo.

Listagem 4. Módulos não Podem ser Instancionados Diretamente e Podem ser Usados Apenas com Classes

irb(main):020:0> module MyModule
irb(main):021:1> def hello
irb(main):022:2> puts "Hello World"
irb(main):023:2> end
irb(main):024:1> end
irb(main):025:0> test = MyModule.new
NoMethodError: undefined method `new' for MyModule:Module
from (irb):25
irb(main):026:0> class MyClass
irb(main):027:1> include MyModule
irb(main):028:1> end
=> MyClass
irb(main):029:0> test = MyClass.new
=> #<MyClass:0x2c18bc8>
irb(main):030:0> test.hello
Hello World
=> nil

Eis uma recapitulação, então: quando você escreve c = Cat.new no Ruby, c é objeto do tipo Cat que é derivado de Object. A classe Cat é um objeto do tipo Class, que é derivado de Módulo, que, por sua vez, é derivado de Object. Tanto o objeto quanto seu tipo são, então, objetos válidos do Ruby.

Métodos Singleton e Classes Editáveis

Agora veja os métodos singleton. Suponha que você queira modelar algo parecido à sociedade humana no C++. Como você faria? Obteria uma classe chamada Humano e depois milhões de objetos de Humano ? Isso é mais parecido a modelar uma sociedade zumbi; cada humano deve ter alguma característica exclusiva. Os métodos singleton do Ruby são úteis aqui, conforme explicado na Listagem 5.

Listagem 5. Métodos Singleton no Ruby

irb(main):113:0> y = Human.new
=> #<Human:0x319b6f0>
irb(main):114:0> def y.paint
irb(main):115:1> puts "Can paint"
irb(main):116:1> end
=> nil
irb(main):117:0> y.paint
Can paint
=> nil
irb(main):118:0> z = Human.new
=> #<Human:0x3153fc0>
irb(main):119:0> z.paint
NoMethodError: undefined method `paint' for #<Human:0x3153fc0>
from (irb):119

Métodos singleton no Ruby são métodos associados apenas a um objeto específico e não estão disponíveis para a classe geral. Eles são prefixados com o nome do objeto. Na Listagem 5, o método paint é específico ao objeto y e y em si; z.paint resulta em um erro de método indefinido. É possível calcular a lista de métodos singleton em um objeto chamando singleton_methods:

irb(main):120:0> y.singleton_methods => ["paint"]

Há ainda outra forma de definir métodos singleton no Ruby. Considere o código da Listagem 6.

Listagem 6. Outra Forma Ainda de Criar Métodos Singleton

irb(main):113:0> y = Human.new => #<Human:0x319b6f0> irb(main):114:0> class << y irb(main):115:1> def sing irb(main):116:1> puts "Can sing" irb(main):117:1> end irb(main):118:1>end => nil irb(main):117:0> y.sing Can sing => nil

Listagem 5 também gera possibilidades interessantes de adição de novos métodos às classes definidas pelo usuário e às classes integradas existentes do Ruby, como String. Isso é impossível no C++ , a menos que você tenha o código fonte para as classes que usa. Consulte a classe String mais uma vez (Listagem 7).

Listagem 7. O Ruby Permite Modificar uma Classe Existente

irb(main):035:0> y = String.new("racecar") => "racecar" irb(main):036:0> y.methods.grep(/palindrome/) => [ ] irb(main):037:0> class String irb(main):038:1> def palindrome? irb(main):039:2> self == self.reverse irb(main):040:2> end irb(main):041:1> end irb(main):050:0> y.palindrome? => true 

Listagem 7 ilustra claramente como é possível editar uma classe existente do Ruby para incluir os métodos de sua preferência. Aqui, incluí o método palindrome? à Sequência . Portanto, as classes do Ruby são editáveis no tempo de execução—uma propriedade eficiente.

Agora que você tem alguma ideia da hierarquia de classes do Ruby e singletons, vamos avançar para self. Observe que usei self ao definir o método palindrome? .

Descobrindo o self

O uso mais comum da palavra-chave self talvez seja declarar um método estático em uma classe do Ruby, como na Listagem 8.

Listagem 8. Usando o self para declarar métodos estáticos de classe

class SelfTest    def self.test       puts "Hello World with self!" end end  class SelfTest2    def test       puts "This is not a class static method"    end end  SelfTest.test SelfTest2.test

Como é possível ver na saída da Listagem 8 – mostrada na Listagem 9 -, não é possível chamar métodos não estáticos sem objetos. O comportamento é semelhante ao C++.

Listagem 9. Erro quando Métodos Não Estáticos são Chamados sem Objetos

irb(main):087:0> SelfTest.test Hello World with self! => nil irb(main):088:0> SelfTest2.test NoMethodError: undefined method 'test' for SelfTest2:Class         from (irb):88

Antes de avançar para usos e significados mais secretos de self, também é possível definir um método estático no Ruby prefixando o nome de classe antes do nome do método:

class TestMe    def TestMe.test        puts "Yet another static member function"    end end  TestMe.test  # works fine

A Listagem 10 oferece um uso mais interessante, mas mais difícil de self.

Listagem 10. Usando Metaclasse para Declarar Métodos Estáticos

class MyTest    class << self       def test         puts "This is a class static method"      end    end end  MyTest.test   # works fine 

Este código define test como um método de classe estático de uma forma um pouco diferente. Para entender o que está acontecendo, veja a sintaxe class << self com mais detalhes. class << self … end cria uma metaclasse. Na cadeia de consulta de método, a metaclasse de um objeto é pesquisada antes de acessar a classe base do objeto. Se você definir um método na metaclasse, ele poderá ser chamado na classe. Isso é semelhante à noção de métodos estáticos noC++.

É possível acessar uma metaclasse? Sim: Basta retornar self de dentro de class << self … end. Observe que, em uma declaração de classe do Ruby, você não tem obrigação de colocar apenas definições de método.A Listagem 11 mostra a metaclasse.

Listagem 11. Obtendo a Metaclasse

irb(main):198:0> class MyTest irb(main):199:1> end => nil irb(main):200:0> y = MyTest.new => #< MyTest:0x2d43fe0> irb(main):201:0> z = class MyTest irb(main):202:1> class << self irb(main):203:2> self irb(main):204:2> end irb(main):205:1> end => #<Class: MyTest >  irb(main):206:0> z.class => Class irb(main):207:0> y.class => MyTest

Voltando ao código na Listagem 7, você verá que palindrome está definido como self == self.reverse. Nesse contexto, self não é diferente de C++. Os métodos em C++ e também no Ruby precisam de um objeto sobre o qual atuar para modificar ou extrair informações de estado. self refere-se a esse objeto aqui. Observe que métodos públicos opcionalmente podem ser chamados prefixando a palavra-chave self , para indicar o objeto sobre o qual o método está atuando, conforme na Listagem 12.

Listagem 12. Usando o self para chamar métodos

irb(main):094:0> class SelfTest3 irb(main):095:1> def foo irb(main):096:2> self.bar() irb(main):097:2> end irb(main):098:1> def bar irb(main):099:2> puts "Testing Self" irb(main):100:2> end irb(main):101:1> end => nil irb(main):102:0> test = SelfTest3.new => #<SelfTest3:0x2d15750> irb(main):103:0> test.foo Testing Self  => nil

Não é possível chamar métodos privados no Ruby com a palavra-chave self prefixada. Para um desenvolvedor do C++ , isso pode ser bastante confuso. O código na Listagem 13 mostra claramente que self não pode ser usado com métodos privados: a chamada para o método privado só deve ser feita com um objeto implícito.

Listagem 13. self não pode ser usado com chamada de método privada

irb(main):110:0> class SelfTest4 irb(main):111:1> def method1 irb(main):112:2> self.method2 irb(main):113:2> end irb(main):114:1> def method3 irb(main):115:2> method2 irb(main):116:2> end irb(main):117:1> private irb(main):118:1> def method2 irb(main):119:2> puts "Inside private method" irb(main):120:2> end irb(main):121:1> end => nil irb(main):122:0> y = SelfTest4.new => #<SelfTest4:0x2c13d80> irb(main):123:0> y.method1 NoMethodError: private method `method2' called for #<SelfTest4:0x2c13d80>         from (irb):112:in `method1' irb(main):124:0> y.method3 Inside private method  => nil

Como tudo no Ruby é um objeto, eis o que você obtém quando chama self no prompt irb :

irb(main):104:0> self => main irb(main):105:0> self.class => Object

No momento em que você ativa irb, o interpretador do Ruby cria o objeto principal para você. Esse objeto principal também é conhecido como o contexto de nível superior na literatura relacionada do Ruby.

Basta de self. Vamos avançar para os métodos dinâmicos e o método method_missing .

O mistério por trás de method_missing

Considere o código do Ruby na Listagem 14.

Listagem 14. method_missing em Ação

irb(main):135:0> class Test irb(main):136:1> def method_missing(method, *args) irb(main):137:2> puts "Method: #{method} Args: (#{args.join(', ')})" irb(main):138:2> end irb(main):139:1> end => nil irb(main):140:0> t = Test.new => #<Test:0x2c7b850> irb(main):141:0> t.f(23) Method: f Args: (23) => nil

Obviamente, se voodoo for seu interesse, a Listagem 14 deverá deixá-lo feliz. O que aconteceu aqui? Você criou um objeto do tipo Testar e depois chamou t.f com 23 como argumento. Mas Testar não possuía f como método, e você deve receber uma mensagem de erro NoMethodError ou semelhante. O Ruby está fazendo algo fascinante aqui: suas chamadas de método estão sendo interceptadas e manipuladas por method_missing. O primeiro argumento paramethod_missing é o nome do método que está ausente—neste caso, f. O segundo e último argumento é *args, que captura os argumentos sendo transmitidos para f. Onde é possível usar algo como isso? Entre outras opções, é possível encaminhar facilmente chamadas de método para um Módulo ou um objeto de componente incluído sem fornecer explicitamente uma interface de programação de aplicativos de wrapper para cada chamada na classe de nível superior.

Veja mais sobre voodoo na Listagem 15.

Listagem 15. Usando o Método de Envio para Transmitir Argumentos para uma Rotina

irb(main):142:0> class Test irb(main):143:1> def method1(s, y) irb(main):144:2> puts "S: #{s} Y: #{y}" irb(main):145:2> end irb(main):146:1> end => nil irb(main):147:0>t = Test.new irb(main):148:0> t.send(:method1, 23, 12) S: 23 Y: 12 => nil

Na Listagem 15, class Test tem um método chamado method1 definido. No entanto, em vez de chamar o método diretamente, você faz uma chamada para o método send . send é um método público da classeObject e, portanto, está disponível para Testar (lembre-se de que todas as classes são derivadas deObject). O primeiro argumento para o método send é um símbolo ou sequência que denota o nome do método. O que o método send pode fazer no curso normal que você não pode? Você pode acessar métodos privados de uma classe usando o método send . Obviamente, a validade desse recurso permanece discutível. Veja o código na Listagem 16.

Listagem 16. Acessando Métodos Privados de Classe

irb(main):258:0> class SendTest irb(main):259:1> private irb(main):260:1> def hello irb(main):261:2> puts "Saying Hello privately" irb(main):262:2> end irb(main):263:1> end => nil irb(main):264:0> y = SendTest.new => #< SendTest:0x2cc52c0> irb(main):265:0> y.hello NoMethodError: private method `hello' called for #< SendTest:0x2cc52c0>         from (irb):265 irb(main):266:0> y.send(:hello) Saying Hello privately => nil

Throw e Catch não são o que parecem

Se você vier de uma experiência de C++ , assim como eu, e tiver a tendência de escrever código seguro contra exceção, então provavelmente comece a se sentir em casa no momento em que veja que o Ruby tem as palavras-chave throw e catch . Infelizmente, o throw e catch têm um significado completamente diferente no Ruby.

O Ruby geralmente manipula exceções usando blocos begin…rescue . Listagem 17 dá um exemplo.

Lista 17. Manipulação de Exceção no Ruby

begin   f = File.open("ruby.txt")   # .. continue file processing  rescue ex => Exception   # .. handle errors, if any  ensure   f.close unless f.nil?   # always execute the code in ensure block  end

Na Listagem 17 , se algo ruim acontece ao tentar abrir o arquivo (talvez um arquivo ausente ou um problema com as permissões do arquivo), o código no bloco rescue é executado. O código no bloco ensure sempre é executado, independentemente se foram emitidas exceções. No entanto, observe que a presença do bloco ensure após o bloco rescue é opcional. Além disso, se uma exceção deve ser emitida explicitamente, então a sintaxe é raise <MyException>. Se você escolher ter sua própria classe de exceção, é recomendável derivar o mesmo da classe de exceção integrada do Ruby para aproveitar os métodos existentes.

O recurso catch-and-throw no Ruby não é de fato uma manipulação de exceção: é possível usar throw para alterar o fluxo do programa. Listagem 18 mostra um exemplo usando throw e catch.

Listagem 18. Throw e Catch no Ruby

irb(main):185:0> catch :label do irb(main):186:1* puts "This will print" irb(main):187:1> throw :label irb(main):188:1> puts "This will not print" irb(main):189:1> end This will print => nil

Na Listagem 18, quando o fluxo de código atinge a instrução throw , a execução é interrompida e o interpretador começa a procurar um bloco catch que manipule o símbolo correspondente. A execução é reiniciada do ponto em que o bloco catch termina. Veja o exemplo de throw e catch na Listagem 19: observe que é fácil difundir as instruções catch e throw entre as funções.

Listagem 19. Manipulação de Exceção no Ruby: Blocos Catch Aninhados

irb(main):190:0> catch :label do irb(main):191:1* catch :label1 do irb(main):192:2* puts "This will print" irb(main):193:2> throw :label irb(main):194:2> puts "This won't print" irb(main):195:2> end irb(main):196:1> puts "Neither will this print" irb(main):197:1> end This will print => nil

Algumas pessoas chegaram ao ponto de dizer que o Ruby utiliza a loucura de C goto até patamares completamente novos com seu suporte catch e throw . Como pode haver diversas camadas aninhadas de funções com os blocos catch possíveis em cada nível, a analogia de loucura de goto parece ter alguma força aqui.

Encadeamentos no Ruby podem ser verdes

O Ruby versão 1.8.7 não suporta simultaneidade real. Ele realmente não suporta. Mas você tem a construção de encadeamento no Ruby, você diria. Sim, você está certo. Mas esse Thread.new não gera um encadeamento de sistema operacional real toda vez que você faz uma chamada para o mesmo. O que o Ruby suporta são encadeamentos verdes: O interpretador do Ruby usa um encadeamento do sistema operacional único para manipular a carga de trabalho de diversos encadeamentos de nível do aplicativo.

Esse conceito de “encadeamento verde” é útil enquanto algum encadeamento está aguardando que ocorra uma E/S, e é possível planejar facilmente um encadeamento do Ruby diferente para fazer bom uso da CPU. Mas essa construção não pode usar uma CPU moderna de diversos núcleos. (A Wikipédia fornece um excelente material que explica o que são encadeamentos verdes. Consulte Recursos para um link).

Este exemplo final (consulte Listagem 20) comprova a questão.

Listagem 20. Diversos Encadeamentos no Ruby

#!/usr/bin/env ruby   def func(id, count)   i = 0;   while (i < count)     puts "Thread #{i} Time: #{Time.now}"     sleep(1)     i = i + 1   end end   puts "Started at #{Time.now}" thread1 = Thread.new{func(1, 100)} thread2 = Thread.new{func(2, 100)} thread3 = Thread.new{func(3, 100)} thread4 = Thread.new{func(4, 100)}  thread1.join thread2.join thread3.join thread4.join puts "Ending at #{Time.now}"

Considerando que você tenha o utilitário top em sua caixa do Linux® ou UNIX® , execute o código em um terminal, obtenha o ID do processo e execute top –p <process id>. Quando top for iniciado, pressione Shift-H para listar o número de encadeamentos em execução. Você deve ver apenas um encadeamento único confirmando o que já sabia: a simultaneidade no Ruby 1.8.7 é um mito.

Apesar de tudo, encadeamentos verdes não são ruins. Eles ainda são úteis em programas vinculados a E/S de trabalho pesado, sem mencionar que essa abordagem é provavelmente a mais portátil entre os sistemas operacionais.

Conclusão

Este artigo cobriu algumas áreas:

  • Noções de hierarquia de classes no Ruby
  • Métodos singleton
  • Decifrando a palavra-chave self e method_missing
  • Exceções
  • Passagem

Independentemente de suas peculiaridades, é divertido programar no Ruby, que é extremamente eficiente em seus recursos para fazer muito com código mínimo. Então, não é de se estranhar que aplicativos de grande escala, como o Twiter, estão usando o Ruby para aproveitar todo o seu potencial. Divirta-se codificando com o Ruby!

Recursos

Aprender

Obter produtos e tecnologias

  • Acesseversão de teste do software IBM (disponível para download ou em DVD) e inove em seu próximo projeto de desenvolvimento de software livre com o uso de softwares específicos para desenvolvedores.

Discutir

Sobre o autor: Arpan Sen é engenheiro líder que trabalha no desenvolvimento de software no segmento de mercado da automação de design eletrônico. Ele tem trabalhado em diversos tipos de UNIX, incluindo Solaris, SunOS, HP-UX e IRIX, além de Linux e Microsoft Windows, por muitos anos. Possui um grande interesse por técnicas de otimização do desempenho de software, teoria de gráfico e computação paralela. Arpan possui pós-doutorado em sistemas de software.