CSC/ECE 517 Fall 2007/wiki1b 4 pm

From Expertiza_Wiki
Revision as of 21:44, 1 October 2007 by Ppadman (talk | contribs) (A Guide to Metaprogramming in Ruby)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Metaprogramming refers to the writing of programs that generates code at run time. It allows us to dynamically add behavior to existing classes and objects.

Ruby is useful for metaprogramming as it is dynamic and reflective. It allows flexibility in writing new control structures. We’ve explored different ways of implementing metaprogramming in Ruby. We’ve listed some of them below, categorized according to the dynamically generated entities:

eval, class_eval, module_eval, instance_eval

Ruby has several evals: eval, class_eval, module_eval, instance_eval. Example using eval:

class MagicLamp

 def self.remember_incantation(incantation)  
   eval "def #{incantation}; puts '#{incantation}!'; end"  
 end  

end

lamp = MagicLamp.new lamp.respond_to? :kazaam # This tests if the object will respond to a method call “kazaam” # This code produces “FALSE” since kazaam doesn’t exist MagicLamp.remember_incantation "kazaam" lamp.respond_to? :kazaam # true lamp.kazaam # "kazaam!" In this example, MagicLamp creates an instance method only when the class method remember_incantation is called. The instance method corresponds to the incantation that the MagicLamp was told to remember. Example using class_eval:

Let’s say we need to define an attribute for a class:

class MyClass

  attr_accessor    :id, :diagram, :telegram

end This code can be refactored to accept attribute names as arguments rather than specifying them (using metaprogramming) as follows: class Class

 def my_attr_accessor( *args )
    args.each do |name|
      self.class_eval do
         attr_accessor :"#{name}"
      end
    end
 end

end

class MyNewClass

 my_attr_accessor :id, :diagram, :telegram

end

Here, the use of metaprogramming is illustrated in they way the attributes are created; ‘my_attr_accessor’ creates an attribute by iterating over the arguments passed to it.

Examples using module_eval: module_eval defines instance and class methods of a class at runtime, when you are outside the class.

Example: Defining an instance method class C end

C.module_eval do

 define_method :wish do  
   p "hello instance method"  
 end  

end

c = C.new c.wish #hello instance method Example: Defining a class method class C end

C.module_eval do

 class << self  
   define_method :wish do  
     p "hello class method"  
     end  
 end  

end

C.wish #hello class method Example: Another form of using module_eval when method body is available as a String object class D

 class << self  
   def method_body  
     ret =<<-EOS  
       def wish  
         p "hello, supplied as String object"  
       end  
     EOS  
   end  
 end  
   
 class C  
 end  
  
 c = C.new  
   
 c.class.module_eval(D.method_body)  
  
 c.wish # hello, supplied as String object  

end

Example using instance_eval The instance_eval method of Object allows you to evaluate a string or block in the context of an instance of a class. One can create a block of code in any context and evaluate it later in the context of an individual instance. In order to set the context, the variable self is set to the instance while the code is executing, giving the code access to the instance's variables. class Navigator

  def initialize
    @page_index = 0
  end
  def next
    @page_index += 1
  end
end
navigator = Navigator.new
navigator.next
navigator.next
navigator.instance_eval "@page_index" #=> 2
navigator.instance_eval { @page_index } #=> 2

Example using define_method class A

    def fred
      puts "In Fred"
    end
    
def create_method(name, &block)
      self.class.send(:define_method, name, &block)
 end

   define_method(:wilma)

{ puts "Charge it!" }

  end
  class B < A

define_method(:barney, instance_method(:fred))

  end
  a = B.new
  a.barney				#In Fred
  a.wilma				#Charge it
  a.create_method(:betty) { p self }
  a.betty				#<B:0x401b39e8>

The above code illustrates how to use metaprogramming by dynamically creating methods using define method. There are two ways of using define method:

define_method(symbol, method) define_method(symbol) { block } Example: The following example shows another implementation of metaprogramming in which the define method gives us a way to bind the attributes of a method to the created methods, thereby changing its parameters. GuineaCounter = Class.new

   shared_count = 0 # A new local variable.
   GuineaCounter.send :define_method, :double_count do
     shared_count += 1
     @count ||= 0
     @count += 1
     [shared_count, @count]
   end
   first_counter = GuineaCounter.new 

second_counter = GuineaCounter.new assert_equal [1, 1], first_counter.double_count assert_equal [2, 2], first_counter.double_count assert_equal [3, 1], second_counter.double_count assert_equal [4, 2], second_counter.double_count As, can be seen, even if the method that defined the local variable shared_count completed execution, the method in GuineaCounter will still be bound to the context of the method. class C

def wish  
   p "hello"  
 end  

end

c = C.new  

c.wish # hello

class D

class << self  
 def keep_some_record  
      p "I am keeping some records"  
     end  
   end  

end

  1. aliasing the wish method

c.class.module_eval do

alias_method :wish_orig, :wish  
 define_method :wish do    
   D.keep_some_record  
   wish_orig  
 end  

end c.wish # I am keeping some records; hello

Example: Using proc def create_proc(&p); p; end create_proc do puts "hello" end # #<Proc ...> p.call(*args) If you want to use the proc for defining methods, you should use lambda to create it, so return and break will behave the way you expect: p = lambda { puts "hoho"; return 1 } define_method(:a, &p) Example: Adding fields based on need for them

class BinaryTree

 def add(value)
   if @root.nil?
     @root = BinaryTreeNode.new(value)
   else
     @root.add(value)
   end
 end

end

class BinaryTreeNode

 def initialize(value)
   @value = value
 end
 def add(value)
   if value < @value
     if @left.nil?
       @left = BinaryTreeNode.new(value)
     else
       @left.add(value)
     end
   else
     if @right.nil?
       @right = BinaryTreeNode.new(value)
     else
       @right.add(value)
     end
   end
 end

end

We see that there are many calls to create new objects of BinaryTree and BinaryTreeNode. Metaprogramming can help replace this code snippet:

if field.nil?

 field = BinaryTreeNode.new(value)

else

 field.add(value)

end

With:

module BinaryTreeHelper

 private
 def add_or_create_node(field, value)
   if instance_variable_get(field).nil?
     instance_variable_set(field,
                           BinaryTreeNode.new(value))
   else
     instance_variable_get(field).add(value)
   end
 end

end

And the classes can be changed accordingly as:

class BinaryTree

 include BinaryTreeHelper
 def add(value)
   add_or_create_node(:@root, value)
 end

end

class BinaryTreeNode

 include BinaryTreeHelper
 def initialize(value)
   @value = value
 end
 def add(value)
   add_or_create_node(value < @value ? :@left : :@right,
                      value)
 end

end

This example is very illustrative in demonstrating how we can generate entities at runtime.

Malvika Dutt Kancherla <mdkanche@ncsu.edu> Priyanka Padmanabhan <ppadman@ncsu.edu> REFERENCES

http://en.wikipedia.org/wiki/Metaprogramming http://rails.aizatto.com/category/language-features/metaprogramming/ http://theplana.wordpress.com/2007/03/12/how-to-define-a-attribute-using-metaprogramming/ http://ozone.wordpress.com/2006/03/02/binary-search-tree-sauce-ruby-part-1/ http://www.ruby-doc.org/core/classes/Object.src/M000366.html http://expressica.com/category/metaprogramming/ http://www.whytheluckystiff.net/articles/seeingMetaclassesClearly.html http://ozone.wordpress.com/2006/02/22/rubybeans-a-short-example-of-ruby-metaprogramming/#comment-1286 http://theplana.wordpress.com/2007/09/16/blocks-parameters-list-and-metaprogramming/http://poignantguide.net/ruby/chapter-6.html <nowiki>Insert non-formatted text here</nowiki>