Se você for um programador C++ e precisar trabalhar no Ruby, necessitará de um pequeno aprendizado. Este artigo discute seis recursos do Ruby que o novato provavelmente entenderá mal, especialmente se vier de um ambiente semelhante, mas não muito, como o
C++:
- A hierarquia de classes do Ruby
- Métodos singleton no Ruby
- A palavra-chave
automaticamente - O método
method_missing - Manipulação de exceção
- Passagem
Observação: Todo o código neste artigo foi testado e baseado no Ruby versão 1.8.7.
A hierarquia de classes no Ruby pode ser complicada. Crie uma classe do tipo
Cat e comece a jogar com sua hierarquia (consulte
Listagem 1).
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.
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. 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? .
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
|
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.
Lista 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.
Lista 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.
Este artigo cobriu algumas áreas:
- Noções de hierarquia de classes no Ruby
- Métodos singleton
- Decifrando a palavra-chave
selfemethod_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!
Aprender
- Leia Programming Ruby: The Pragmatic Programmers' Guide (Dave Thomas, Chad Fowler e Andy Hunt; 2ª edição), uma leitura obrigatória do Ruby que é popularmente conhecida como a caixa Pickaxe.
- Veja este outro recurso inestimável do Ruby, The Ruby Programming Language [Yukihiro "Matz" Matsumoto (criador do Ruby) e David Flanagan, O'Reilly, 2008].
- Visite To Ruby From C and C++, um excelente site para programadores do
C/C++que querem aprender sobre o Ruby. - Saiba mais sobre encadeamentos verdes em uma boa explicação na Wikipédia.
- No menu suspenso Software Livre do developerWorks, encontre informações abrangentes como instruções, ferramentas e atualizações de projetos que ajudam os desenvolvedores com tecnologias de software livre para usá-las com os produtos IBM.
- No menu suspenso área do Linux no developerWorks, encontre vários artigos de instruções e tutoriais, além de downloads, fóruns de discussão e uma variedade de outros recursos para desenvolvedores a administradores Linux.
- Fique por dentro dosEventos técnicos e webcasts do developerWorks com ênfase em uma série de produtos IBM e tópicos do segmento de mercado de TI.
- Participe de um briefing gratuito do developerWorks para inteirar-se sobre os produtos e ferramentas IBM, além das tendências do mercado de TI.
- Escute os Podcasts do developerWorks para obter entrevistas interessantes e
discussões para os desenvolvedores de software.
- Siga o developerWorks no Twitter.
- Acompanhe as Demos do developerWorks que variam de instalação e configuração de produtos para iniciantes a funcionalidades avançadas para desenvolvedores experientes.
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
- Conecte-se com outros usuários do
developerWorks enquanto explora os blogs, fóruns, grupos e wikis voltados para desenvolvedores. Ajude a desenvolver o software livre do mundo real na comunidade do developerWorks.
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.