CSC/ECE 517 Fall 2007/wiki1b 4 am
Metaprogramming in Ruby
Ruby is a dynamically-typed, interpreted, object oriented language. One of the unique aspects of Ruby is its extensive reflexive metaprogramming facilities. According to H. Conrad Cunningham, Professor in the Department of Computer and Information Science, “Reflexive programming is the capability of a program to both inspect and change its own program structures.”[1] He goes hello on to so that “it has long been a staple of languages such as Smalltalk and Lisp but the recent interest in Ruby has renewed interest in metaprogramming and in techniques like internal DSL.”[2]
What is Metaprogramming?
According to lecture 8 of our class lectures “the related technique of metaprogramming allows one to create new program entities, such as method or classes, at run time. Why is this called metaprogramming? Essentially the program is writing a program.” In a more detailed description of metaprogramming we can turn to wikipedia.org. “Metaprogramming is the writing of computer programs that write or manipulate other programs (or themselves) as their data or that do part of the work during compile time that is otherwise done at run time. In many cases, this allows programmers to get more done in the same amount of time as they would take to write all the code manually.”[3] This is the essential message learned throughout our discovery of metaprogramming. It is a program writing a program. Following are a dozen examples of metaprogramming in Ruby. The examples have been broken down into several catagories to better examine the different aspects of metaprogramming in Ruby.
Examples of Metaprogramming
One of the first artilces that I read concerning metaprogramming in Ruby was posted on a blog by Ola Bini[4] wrote somewhat of a guide on metaprogramming in Ruby and broke it down into 11 different techniques used by metaprogramming. Although, I haven’t decided to use his categories verbatum, he did leave a good outline when trying to understand the power generated through metaprogramming. I have decided to breakdown this discussion of metaprogramming in Ruby into 4 categories:
1. Using the different versions of eval; 2. Create Procs from blocks and send them around; 3. Create classes and modules dynamically; 4. Using the Singleton Class
Using the Different Versions of eval
Bini in his article, goes on to explain the different eval’s that are availble and the importance of knowing what each of them do and when to use them. The major difference is that eval will evalute a string while the others can evaluate a block. Eval, instance_eval, module_eval and class_eval are some that he mentioned. “Instance eval will evaluate the string or block in the context of the receiver, meaning self will be set to the receiver while evaluating.”[4] Module_eval will use the context of the module to do the evaluation. For example, String.instance_eval with a def foo inside will be available as String.foo, while String.module_eval will be String.new.foo instead. Although Bini didn’t give an extensive example when illustrating the different eval’s, another example was found on Ozone.com in an article entitled “RubyBeans, a short example of Ruby metaprogramming.” [5] In the article, the author is trying to explaining why implementing a JavaBean would benefit greatly by metaprogramming in Ruby. At the end of the article, however, is the question of how to create static methods through metaprogramming. The example of a class that counts the number of properties that have been assigned was given.
Example 1
test = TestBean.new test.name = “parker” test2 = TestBean.new test2.name = “lewis” TestBean.name_count # => 2 TestBean.firstname_count # => 0
The solution for Ruby was to us class_eval, although he does state that there are more elegant ways of doing it. This is just one exmple on how implement the eval statement in metaprogramming.
Example 2
1. def RubyBean.property(*properties) 2. properties.each { |property| 3. class_eval(%Q[ 4. @@#{property}_count = 0 5. 6. def #{property} 7. @#{property} 8. end 9. 10. def #{property}=(value) 11. oldValue = @#{property} 12. return if (value == oldValue) 13. @listeners.each { |listener| 14. listener.propertyChanged(:#{property}, 15. oldValue, 16. value) 17. } 18. @#{property} = value 19. @@#{property}_count += 1 20. end 21. 22. def self.#{property}_count 23. @@#{property}_count 24. end 25. ]) 26. } 27. end
I also found an article on Devsource that gave an extensive example of the powers of metaprogramming in Ruby. The article “An Exercixe in Metaprogramming with Ruby” [6] starts with a simple example and continues to expand it. For the purposes of this paper I will only use the first part of his exmple. He first states his assumptions that we want to create a class with a name derived from a file name and the first line of the file is comma separate list of attribute name. [6]
Example 3
We are given a file and we want to create a class with its contents. First he creates a new class. class_name = File.basename(file_name,".txt").capitalize # e.g. "foo.txt" => "Foo" klass = Object.const_set(class_name,Class.new) He then goes on to add the attributes in the form of an array. data = File.new(file_name) names = data.gets.chomp.split(",") # an array of strings Then he is able to use the class_eval in the context of the new class klass. klass.class_eval do attr_accessor *names
This is again using class_eval in this example to metaprogramming, creating all the necessary accessor methods.
Create Procs from Blocks and Send Them Around
In the article mentioned earlier, Bini statest that creating Procs is what makes many API’s easy to use and one way Markaby uses to manage the CSS class definitions. [4] He illustrates how to turn a block into a Proc:
Example 1
def create_proc(&p); p; end create_proc do puts "hello" end # => #<Proc ...> And to call it is also very simple. When using proc for defining methods we should use lambda to create it. p.call(*args) p = lambda { puts "hoho"; return 1 } define_method(:a, &p)
Again this is a very important aspect of metaprogramming in Ruby. In an article by Daniel Bernier, “Ruby, Meta-Programming, and Waitr” another example of using proc is given. [6] A simple method is present as follows:
Example 2
def divs (*args, &proc) proc ||= make_filter(args) # Find all sub-nodes for which this block evaluates to 'true' self.find_all { |n| n.tagName.upcase == "div".upcase and proc.call(n) } end
This is a simple example where &proc is used as a parameter in the method definition and it can now be treated as a variable. It is no longer called with the yield but instead the call is made with proc.call. This again illustrates the power of metaprogramming allowing the programmer to pass this block as a variable.
Creating Classes and Modules Dynamically
Being able create things dynamically, is essential to the Ruby programming language. You can do pretty much any thing to a non-frozen class or module. [4] Bini uses the example of a Struct class to show this.
Example 1
PersonVO = Struct.new(:name, :phone, :email) p1 = PersonVO.new(:name => "Ola Bini") Once you’ve done this, creating a new class and method is as follows: c = Class.new c.class_eval do define_method :foo do puts "Hello World" end end c.new.foo # => "Hello World"
Again we are using class_eval to create this new class. This is a very simple example of how to create a module or method dynamically. There is an article on bias2build.com that also discussing the ability to create classes and modules dynamically.[8] Although no coded example is given the information is very useful. He talks specifically about define_method and alias_method that can be used to create a wrapper around a class. This can in turn can be used to change the functionality of a method. The ability to do this gives Ruby metaprogramming its strength. An Ariel Ortiz article on linuxjournal.com outlined another aspect to dynamic programming in relation to metaprogramming. [9] What Ortiz points out is that we are able to “modify different parts of your program easily during runtime without having to generate source code explicitly as we did previously.” [9] The example he cites is the use of the attr_accessor to produce the read/write methods automatically. The code is as follows:
Example 2
class Person attr_accessor :name end Expanded out it would look like this: class Person def name @name end def name=(new_name) @name = new_name end end
This truly shows the power of dynamic operations.
Using the Singleton Class
Bini asserts that the use of the Singleton class is very helpful in implementing different aspects of metaprogramming. Being able to manipulate single objects through the singleton class is essential. [4] An easy way to get to the singleton class is as follows:
Example 1
sclass = (class << self; self; end) Another definition is as follows: module Kernel def singleton_class class << self; self; end end end
Another example of using a singleton class is presented in an article on bias2build.com. [8] The author states that “Singleton classes are used to add methods or attributes to instances of a class rather than to classes. Ruby's syntax allows the programmer to either define individual methods of the singleton class, or to pry open the singleton class and add methods or variables that way.” [8] Having examples of the singleton implementation can be useful, like as follows.
Example 2
greeting = "Hello" bob = "Bob" def greeting.say_twice puts self puts self end greeting.say_twice # This will print "Hello" twice bob.say_twice # This will throw a NoMethodError
True Power of Meta-programming
Rubyonrails.com gives a great example of the power of metaprogramming. [7] The author gives the example of having to test some algorithms he was working on trees. He then explored how the code would work for this for a simple binary tree in Java, XML and Ruby. The Java code took 15 lines of code, while the XML took 11 lines of code. He was then able to drive the number of lines of code down to 4 with the help of metaprogramming through Ruby. Althought the Ruby cod might need some work, he admits, he states “Metaprogramming is a way to drive Ruby with Ruby.” [7] Metaprogramming truly is powerful while implemented with the techniques discussed above. Although this is not a comprehensive representation of metaprogramming many of the links provided can assurdly push any new programmer in Ruby in the right direction.
REFERENCES:
[1] http://ftp.cs.olemiss.edu/~hcc/papers/RubyMetaprogramming.pdf
[2] http://ftp.cs.olemiss.edu/~hcc/papers/RubyMetaprogramming.pdf
[3] http://en.wikipedia.org/wiki/Metaprogramming
[4] http://ola-bini.blogspot.com/2006/09/ruby-metaprogramming-techniques.html
[5] http://ozone.wordpress.com/2006/02/22/rubybeans-a-short-example-of-ruby-metaprogramming/
[6] http://invisibleblocks.wordpress.com/2006/06/14/ruby-meta-programming-and-watir/
[7] http://www.rubyrailways.com/sometimes-less-is-more/