Para quem como eu, vem de linguagens não-dinâmicas, o que mais impressiona (e dificulta no início) é a capacidade de Ruby em se moldar e executar códigos dinamicamente. E seguindo a nossa saga pelos conceitos de metaprogramação, vamos explorar um pouco mais um recurso interessantíssimo de Ruby chamado eval.
Ruby Eval
O nome eval tem origem na palavra inglesa evaluation, que em uma tradução livre significa “avaliar o valor de algo”, no caso de Ruby, o valor de uma expressão ou trecho de código.
No caso de Ruby, a melhor definição seria “interpretar”, devido ao fato que além de avaliar, o código recebido também é executado, como no exemplo abaixo:
eval("puts 'Hello World'") # => Hello World
No post anterior sobre metaprogramação, eu utilizei o método instance_variable_set da classe Object.
Esse método pode ser utilizado para evitar repetições de código. No código abaixo eu mostro como é a implementação desse método e como ele é usado na prática:
Implementação redundante de um construtor de uma classe:
class CalendarEvent
def initialize(start_time=0, end_time=0, attendees=0)
@start_time = start_time
@end_time = end_time
@attendees = attendees
end
end
Implementação melhorada com o uso de eval:
#Primeiro, adicionamos o seguinte código à classe Object,
#para torná-lo disponível para as instâncias de quaisquer classe
class Object
private
def set_instance_variables(binding, *variables)
variables.each do |var|
instance_variable_set("@#{var}", eval(var, binding))
end
end
end
#Depois podemos melhorar a nossa classe
class CalendarEvent
def initialize(start_time=0, end_time=0, attendees=0)
set_instance_variables(binding, *local_variables)
end
end
c = CalendarEvent.new(4,5,2)
puts c.instance_eval "@start_time" # => 4
Em Ruby, temos mais alguns métodos do tipo eval disponível:
* instance_eval
* class_eval
* module_eval
Dentre esses, a diferença básica está no escopo aonde os métodos serão executados: em classes ou instâncias.
Module.class_eval
O método da classe Module, chamado class_eval, nos possibilita executar blocos dentro do escopo de uma classe. Uma possibilidade útil criada por esse método é a de criar dinamicamente métodos a uma classe, como no exemplo do attr_accessor, que é implementado em C, mas se fosse implementado em Ruby seria algo do tipo:
class Class
def attr_acc(*vars)
return if vars.empty?
code = ""
vars.each do |v|
# Cria gets para as variaveis
code << "def #{v}; @#{v}; end;"
# Cria sets para as variaveis
code << "def #{v}=(value); @#{v} = value; end;"
end
self.class_eval code
end
end
#podemos criar uma classe que utiliza nossa cópia do attr_acc
class Foo
attr_acc :bar
end
#... e testar pra ver se ela funciona
f = Foo.new
f.bar = "hello"
puts f.bar # => hello
Module.module_eval
Na verdade, podemos realizar algo parecido com o exemplo anterior com uma sintaxe levemente diferente usando Mocule.module_eval como no exemplo abaixo, aonde eu crio um método que retorna os 5 primeiro múltiplos de 3:
Fixnum.module_eval do
def self.multiple_of_three
[3,6,9,12,15]
end
end
puts Fixnum.multiple_of_three
#=> 3
6
9
12
15
É importante ressaltar que no exemplo acima, estamos criando dinamicamente um método na classe Fixnum, e não um método para as instâncias dessa classe, de modo que a chamada abaixo acabará nos retornando uma exceção:
5.multiple_of_three # =>NoMethodError: undefined method `multiple_of_three' for 5:Fixnum
Object.instance_eval
O método intance_eval, da classe Object, por sua vez implementa a função eval sob o escopo de instâncias.
class Foo
def initialize
@bar = 'hey'
end
def change_bar
@bar = "ho!"
end
end
foo = Foo.new
puts foo.instance_eval "@bar"
foo.change_bar
puts foo.instance_eval "@bar"
Conclusão
Metaprogramação acaba ficando mais claro na medida que começamos a compreender o verdadeiro poder oferecido pela linguagem. Uma ótima fonte de inspiração e estudo é o código de projetos largamente utilizados em Ruby.
É com recursos como esse que o Rails (e outras ferramentas escritas em Ruby) adicionam diversas funcionalidades ao core de Ruby para comtemplar suas “mágicas”.
This entry has no comments
You have a wonderful opportunity to be the first to comment!