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

    def getName
        return @name
    end

    def setName(name)
        @name = name
    end

    def getAge
        return @age
    end

    def setAge(age)
        @age = age
    end
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 "self.class is #{self.class}"
        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

            setter = Proc.new do |value|
                self.instance_variable_set(fieldname_sym, value)
            end
            self.send(:define_method, "set_#{ name }", setter)

            getter = Proc.new do
                return self.instance_variable_get(fieldname_sym)
            end
            self.send(:define_method, "get_#{ name }", getter)
        end
    end
end

class Person
    gen_getters_and_setters :name, :age

    def initialize(name, age)
        @name = name
        @age = age
    end

    # Note: getters and setters not defined explicitly!
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.)