5
$\begingroup$

When I teach metaprogramming like dynamically defining methods and method delegation, students usually understand what it is fairly quickly because it's simply a dynamic way of doing something they've already done. I now want to assign them a project that utilizes the metaprogamming I've taught. I'm having trouble thinking of a project to assign them which would be made significantly easier by using metaprogramming. The only use I know of is in a DSL. Creating a DSL is a rather large project to assign a group of high school students, so I'd prefer something smaller.

Specifically this project is for fairly advanced students working with ruby, but more general answers are better.

So, what projects could you assign to illustrate the utility of metaprogramming?

$\endgroup$
1
  • 1
    $\begingroup$ Could you not create a self-learning maze navigator? Natural lead in to machine learning and AI. $\endgroup$ Commented Jun 7, 2017 at 5:50

3 Answers 3

4
$\begingroup$

During lecture, my students and I recreate Javascript objects in Ruby. In Javascript, these two lines are synonymous:

config['indent'] = 2;
config.indent = 2;

Ruby doesn't support this duality natively, but metaprogramming makes it possible.

We create a class AutoObject that internally stores a dictionary to hold all of the object's properties. Its method_missing consults this dictionary when something like config.indent is executed. If the method name suggests an assignment, it updates the dictionary. If it's a read of an existing property, it returns the value. Otherwise we raise an exception.

Once the automatic properties are in place, it's not much more work to add serialization and file I/O. By the end, we are reading and writing configuration files that are useful for any application.

ActiveRecord does something similar when it automatically creates an object and its interface based only on the database schema.

$\endgroup$
2
$\begingroup$

The obvious introductory example would be re-implementing Module#attr_reader / Module#attr_writer / Module#attr_accessor, which after all are typically the first example of metaprogramming a Ruby programmer comes in contact with:

module MyAttrAccessor
  def attr_reader(*attrs)
    attrs.each do |attr|
      define_method(attr) do p instance_variable_get(:"@#{attr}") end
    end
  end

  def attr_writer(*attrs)
    attrs.each do |attr|
      define_method(:"#{attr}=") do |val| p instance_variable_set(:"@#{attr}", val) end
    end
  end

  def attr_accessor(*attrs)
    attrs.each do |attr|
      p attr
      attr_reader attr
      attr_writer attr
    end
  end

  Module.prepend self
end

Then, you could add validation, for example, which dynamically looks for validator methods named after the attribute, i.e.

validated_attr_writer :foo

would create a method foo= which sets an instance variable @foo, but only if it passes validation, which is defined as a set of methods named foo_must_be_*.

The result could look something like this:

class Module
  def validated_attr_writer(*attrs)
    attrs.each do |attr|
      define_method(:"#{attr}=") do |val|
        raise ArgumentError unless methods.grep(/^#{attr}_must_be/).all? {|meth| 
          send(meth, val) }
        instance_variable_set(:"@#{attr}", val)
      end
    end
  end
end
$\endgroup$
1
$\begingroup$

A documentation system similar to Python's, where documentation isn't stored in the source text, but as dynamic data inside of instance variables of the documented objects themselves.

This also allows you to talk about Ruby's object model, e.g. the fact that methods aren't objects and thus you can't store the documentation of a method inside the method, you have to store it somewhere else (e.g. in the module the method is defined in).

The API looks a bit like this:

class Foo
  doc 'This method does something totally interesting.'
  def bar(*) end
end

Foo.show_doc(:bar)
#=> 'This method does something totally interesting.'

The implementation looks something like this:

module MethodAddedHook
  private

  def method_added(meth)
    (@__doc__ ||= {})[meth] = @__last_doc__ if @__last_doc__
    @__last_doc__ = nil
    super
  end
end

class Module
  prepend MethodAddedHook

  def show_doc(meth)
    instance_variable_get(:@__doc__)[meth]
  end

  private

  def doc(str)
    @__last_doc__ = str
  end
end
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.