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

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:

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:

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:

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.)