CSC/ECE 517 Fall 2007/wiki1b 4 pm: Difference between revisions
No edit summary |
No edit summary |
||
Line 13: | Line 13: | ||
We’ve explored different ways of implementing metaprogramming in Ruby. Here is a guide to learning Metaprogramming by example. | We’ve explored different ways of implementing metaprogramming in Ruby. Here is a guide to learning Metaprogramming by example. | ||
Ruby has several evals: eval, class_eval, module_eval, instance_eval. | There are several methods to implement Metaprogramming. These can be classified into the following categories: | ||
Metaprogramming with eval, class_eval, module_eval, instance_eval | |||
Metaprogramming with define_method | |||
Metaprogramming with proc | |||
Metaprogramming with instance_variable_set/ instance_variable_get | |||
== Metaprogramming with eval, class_eval, module_eval, instance_eval == | |||
Here are a few examples using evals. Ruby has several evals: eval, class_eval, module_eval, instance_eval. | |||
'''Example using eval:''' | '''Example using eval:''' | ||
Line 29: | Line 41: | ||
# This code produces “FALSE” since kazaam doesn’t exist | # This code produces “FALSE” since kazaam doesn’t exist | ||
MagicLamp.remember_incantation "kazaam" | MagicLamp.remember_incantation "kazaam" | ||
lamp.respond_to? :kazaam # true | lamp.respond_to? :kazaam # true | ||
lamp.kazaam # "kazaam!" | 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. | 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: | Example using class_eval: | ||
Let’s say we need to define an attribute for a class: | Let’s say we need to define an attribute for a class: | ||
class MyClass | class MyClass | ||
attr_accessor :id, :diagram, :telegram | |||
end | end | ||
This code can be refactored to accept attribute names as arguments rather than specifying them (using metaprogramming) as follows: | This code can be refactored to accept attribute names as arguments rather than specifying them (using metaprogramming) as follows: | ||
class Class | |||
class Class | |||
def my_attr_accessor( *args ) | |||
args.each do |name| | |||
self.class_eval do | |||
attr_accessor :"#{name}" | |||
end | |||
end | end | ||
end | |||
end | end | ||
class MyNewClass | class MyNewClass | ||
Line 58: | Line 74: | ||
Examples using module_eval: | Examples using module_eval: | ||
module_eval defines instance and class methods of a class at runtime, when you are outside the class. | module_eval defines instance and class methods of a class at runtime, when you are outside the class. | ||
Example: Defining an instance method | Example: Defining an instance method | ||
class C | class C | ||
end | end | ||
C.module_eval do | C.module_eval do | ||
define_method :wish do | |||
p "hello instance method" | |||
end | |||
end | end | ||
c = C.new | c = C.new | ||
c.wish #hello instance method | c.wish #hello instance method | ||
Example: Defining a class method | Example: Defining a class method | ||
class C | |||
end | class C | ||
end | |||
C.module_eval do | C.module_eval do | ||
class << self | |||
define_method :wish do | |||
p "hello class method" | |||
end | |||
end | |||
end | end | ||
C.wish #hello class method | C.wish #hello class method | ||
Example: Another form of using module_eval when method body is available as a String object | Example: Another form of using module_eval when method body is available as a String object | ||
class D | |||
class D | |||
class << self | |||
def method_body | |||
ret =<<-EOS | |||
def wish | |||
p "hello, supplied as String object" | |||
end | |||
EOS | |||
end | |||
end | end | ||
class C | |||
end | |||
c = C.new | |||
c.class.module_eval(D.method_body) | |||
c.wish # hello, supplied as String object | |||
end | end | ||
Example using instance_eval | 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. | 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 | class Navigator | ||
def initialize | |||
@page_index = | @page_index = 0 | ||
end | |||
def next | |||
@page_index += 1 | |||
end | |||
end | end | ||
navigator = Navigator.new | |||
navigator.next | |||
navigator.next | |||
navigator.instance_eval "@page_index" #=> 2 | |||
navigator.instance_eval { @page_index } #=> 2 | |||
== Metaprogramming with define_method == | |||
Example using define_method | Example using define_method | ||
class A | class A | ||
def fred | def fred | ||
puts "In Fred" | puts "In Fred" | ||
end | end | ||
def create_method(name, &block) | |||
self.class.send(:define_method, name, &block) | self.class.send(:define_method, name, &block) | ||
end | end | ||
Line 151: | Line 174: | ||
a.create_method(:betty) { p self } | a.create_method(:betty) { p self } | ||
a.betty #<B:0x401b39e8> | a.betty #<B:0x401b39e8> | ||
The above code illustrates how to use metaprogramming by dynamically creating methods using define method. | The above code illustrates how to use metaprogramming by dynamically creating methods using define method. | ||
There are two ways of using define method: | There are two ways of using define method: | ||
define_method(symbol, method) | define_method(symbol, method) | ||
define_method(symbol) { block } | 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. | 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 | |||
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 | |||
second_counter = GuineaCounter.new | first_counter = GuineaCounter.new | ||
assert_equal [1, 1], first_counter.double_count | second_counter = GuineaCounter.new | ||
assert_equal [2, 2], first_counter.double_count | assert_equal [1, 1], first_counter.double_count | ||
assert_equal [3, 1], second_counter.double_count | assert_equal [2, 2], first_counter.double_count | ||
assert_equal [4, 2], second_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. | 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 | |||
class C | |||
def wish | |||
p "hello" | |||
end | |||
end | end | ||
c = C.new | |||
c.wish # hello | |||
c.wish # hello | |||
class D | class D | ||
class << self | |||
def keep_some_record | |||
p "I am keeping some records" | |||
end | |||
end | end | ||
end | |||
# aliasing the wish method | # aliasing the wish method | ||
c.class.module_eval do | c.class.module_eval do | ||
alias_method :wish_orig, :wish | |||
define_method :wish do | |||
D.keep_some_record | |||
wish_orig | |||
end | |||
end | end | ||
c.wish # I am keeping some records; hello | c.wish # I am keeping some records; hello | ||
== Metaprogramming with proc == | |||
Example: Using proc | Example: Using proc | ||
def create_proc(&p); p; end | def create_proc(&p); p; end | ||
create_proc do | create_proc do | ||
puts "hello" | |||
end # #<Proc ...> | end # #<Proc ...> | ||
p.call(*args) | 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: | 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) | p = lambda { puts "hoho"; return 1 } | ||
define_method(:a, &p) | |||
== Metaprogramming with instance_variable_set/ instance_variable_get == | |||
Example: Adding fields based on need for them | Example: Adding fields based on need for them | ||
class BinaryTree | class BinaryTree | ||
def add(value) | |||
if @root.nil? | |||
@root = BinaryTreeNode.new(value) | |||
else | |||
@root.add(value) | |||
end | |||
end | end | ||
end | end | ||
class BinaryTreeNode | 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 | else | ||
@right.add(value) | if @right.nil? | ||
@right = BinaryTreeNode.new(value) | |||
else | |||
@right.add(value) | |||
end | |||
end | end | ||
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: | 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? | if field.nil? | ||
field = BinaryTreeNode.new(value) | |||
else | else | ||
field.add(value) | |||
end | end | ||
With: | With: | ||
module BinaryTreeHelper | 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 | ||
end | end | ||
And the classes can be changed accordingly as: | And the classes can be changed accordingly as: | ||
class BinaryTree | class BinaryTree | ||
include BinaryTreeHelper | |||
def add(value) | |||
add_or_create_node(:@root, value) | |||
end | |||
end | end | ||
class BinaryTreeNode | class BinaryTreeNode | ||
include BinaryTreeHelper | |||
def initialize(value) | |||
@value = value | |||
end | |||
def add(value) | |||
add_or_create_node(value < @value ? :@left : :@right, | |||
value) | |||
end | |||
end | end | ||
This example is very illustrative in demonstrating how we can generate entities at runtime. | This example is very illustrative in demonstrating how we can generate entities at runtime. | ||
== '''REFERENCES''' == | |||
http://en.wikipedia.org/wiki/Metaprogramming | http://en.wikipedia.org/wiki/Metaprogramming | ||
Line 302: | Line 336: | ||
http://ozone.wordpress.com/2006/02/22/rubybeans-a-short-example-of-ruby-metaprogramming/#comment-1286 | 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 | http://theplana.wordpress.com/2007/09/16/blocks-parameters-list-and-metaprogramming/http://poignantguide.net/ruby/chapter-6.html | ||
Revision as of 18:03, 10 October 2007
Introduction
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.
Problem Definition
Our problem states: “There are many good examples of metaprogramming in Ruby on the Web. Take a look at, say, a dozen of them, and write a guide to them. Classify them into whatever categories are appropriate, and give recommendations on how to proceed through them to acquire a good knowledge of the uses and usefulness of metaprogramming.”
Implementing Metaprogramming
We’ve explored different ways of implementing metaprogramming in Ruby. Here is a guide to learning Metaprogramming by example.
There are several methods to implement Metaprogramming. These can be classified into the following categories:
Metaprogramming with eval, class_eval, module_eval, instance_eval
Metaprogramming with define_method
Metaprogramming with proc
Metaprogramming with instance_variable_set/ instance_variable_get
Metaprogramming with eval, class_eval, module_eval, instance_eval
Here are a few examples using evals. 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 #false
- 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
Metaprogramming with define_method
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
- 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
Metaprogramming with proc
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)
Metaprogramming with instance_variable_set/ instance_variable_get
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.
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