Code examples: meta.rb, meta2.rb
Blocks and Procs
In Ruby, you can create an object that is a handle to a block by creating a Proc object:
add1 = Proc.new {|x| x + 1}
puts add1.call(3) # prints 4
A Proc acts as an anonymous function.
Metaprogramming
Metaprogramming is the ability for a program to create new functions/methods when the program runs.
Ruby has excellent support for metaprogramming because
- Methods can be added to classes and objects at runtime
- Procs allow code to be "saved" and executed later
Motivation
Most classes will need getter and setter methods:
class Person
def initialize(name, age)
@name = name
@age = age
end
<span class="k">def</span> <span class="nf">getName</span>
<span class="k">return</span> <span class="vi">@name</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">setName</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
<span class="vi">@name</span> <span class="o">=</span> <span class="nb">name</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">getAge</span>
<span class="k">return</span> <span class="vi">@age</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">setAge</span><span class="p">(</span><span class="n">age</span><span class="p">)</span>
<span class="vi">@age</span> <span class="o">=</span> <span class="n">age</span>
<span class="k">end</span>
end
Defining getters and setters is tedious: they all look the same! They are also a source of unnecessary bugs: if we accidentally refer to the wrong field within a getter or setter, it won't work as expected.
Metaprogramming to the rescue
Wouldn't it be nice if we could generate getters and setters as needed?
Recall two unusual characteristics of Ruby:
- All classes are "open": the program can add new methods to any class at any time
- Class declarations can contain executable statements: these are treated as method calls on the class object
Example:
#! /usr/bin/ruby
class Object def self.sayhello puts "Hello!" puts "This class is #{self.name}" end end
class Person sayhello end
class Vehicle sayhello end
The output of the program is
Hello!
This class is Person
Hello!
This class is Vehicle
Things to note:
- This code adds a new class method (i.e., static method) called sayhello to the built-in Object class, which is a superclass of every other class
- The Person and Vehicle classes call this method in their declarations
- When executed, the sayhello method is called on the Class object for the class being declared
Now, the stage is set for metaprogramming: what if a class called a method as part of its declaration (like the calls to sayhello above, but that method added more methods to the class? Now we are metaprogramming!
Let's use metaprogramming to generate getters and setters for fields whose names are specified by a list of symbols.
#! /usr/bin/ruby
class Object def self.gen_getters_and_setters(*names) names.each do |name| fieldname_sym = "@#{ name }".to_sym
<span class="n">setter</span> <span class="o">=</span> <span class="no">Proc</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">value</span><span class="o">|</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">instance_variable_set</span><span class="p">(</span><span class="n">fieldname_sym</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
<span class="k">end</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="ss">:define_method</span><span class="p">,</span> <span class="s2">"set_</span><span class="si">#{</span> <span class="nb">name</span> <span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="n">setter</span><span class="p">)</span>
<span class="n">getter</span> <span class="o">=</span> <span class="no">Proc</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span>
<span class="k">return</span> <span class="nb">self</span><span class="p">.</span><span class="nf">instance_variable_get</span><span class="p">(</span><span class="n">fieldname_sym</span><span class="p">)</span>
<span class="k">end</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="ss">:define_method</span><span class="p">,</span> <span class="s2">"get_</span><span class="si">#{</span> <span class="nb">name</span> <span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="n">getter</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
end
class Person gen_getters_and_setters :name, :age
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">age</span><span class="p">)</span>
<span class="vi">@name</span> <span class="o">=</span> <span class="nb">name</span>
<span class="vi">@age</span> <span class="o">=</span> <span class="n">age</span>
<span class="k">end</span>
<span class="c1"># Note: getters and setters not defined explicitly!</span>
end
p = Person::new("Dave", 41) puts "Original age is: #{p.get_age}"
p.set_age(42) puts "Happy birthday, your age is now #{p.get_age}"
A few things to note:
- The asterisk on the *names parameter allows it to capture a variable number of arguments
- The instance_variable_set and instance_variable_get methods allow access to a field whose name is specified as a symbol (which we compute by constructing a string equal to the field name and converting the string to a symnol)
- The define_method method of the Class class defines a new instance method. Because it is a private method, we can't call it directly, but instead must use the class object's send method to call it indirectly. (This appears to be necessary because define_method is private, and can't be called directly.)
Metaprogramming in real life
In practice, you won't have to define facilities like the gen_getters_and_setters method described above. Ruby already provides similar facilities: see attr_reader and attr_accessor in Ruby's Module class. (Module is the superclass of Class: so, these facilities are available to all Ruby classes and mixins.)