CSC/ECE 517 Fall 2011/ch4 4g as: Difference between revisions
No edit summary |
No edit summary |
||
(22 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
In this page, the concept of | In this page, the concept of meta-programming in Ruby is discussed. We first give the definition of meta-programming, along with a brief description. Next, we give an overview of the concept of a meta-program in general i.e., its evolution and implementation in various languages is discussed. We then, discuss about the implementation of the concept of meta-programming in a dynamic and object-oriented programming language called Ruby. | ||
== Definitions == | == Definitions == | ||
A program that manipulates its code or other program’s code as its data is called a meta-program. An established example of meta-program is a [http://en.wikipedia.org/wiki/Compiler compiler]. The act of writing these meta-programs, that write or manipulate other programs or themselves as data is called [http://en.wikipedia.org/wiki/Metaprogramming | A program that manipulates its code or other program’s code as its data is called a meta-program. An established example of meta-program is a [http://en.wikipedia.org/wiki/Compiler compiler]. The act of writing these meta-programs, that write or manipulate other programs or themselves as data is called [http://en.wikipedia.org/wiki/Metaprogramming meta-programming]. | ||
We can also define meta-program as code | We can also define a meta-program as a program which generates code and writing such programs is called meta-programming. | ||
This allows programmers to get more done in the same amount of time as they would take to write all the code manually, or it gives programs greater flexibility to efficiently handle new situations without recompilation. | This allows programmers to get more done in the same amount of time as they would take to write all the code manually, or it gives programs greater flexibility to efficiently handle new situations without recompilation. | ||
Line 13: | Line 13: | ||
Let us look at some of the uses of meta-programming:<ref>[http://c2.com/cgi/wiki?MetaProgramming Overview of uses of Meta Programming]</ref> | Let us look at some of the uses of meta-programming:<ref>[http://c2.com/cgi/wiki?MetaProgramming Overview of uses of Meta Programming]</ref> | ||
1) The major use of meta-programming is that it pre-generates the code at run-time. For example if we are writing an application and we want a quick lookup table containing keys and values associated with the keys, we can have the program build the table at startup | 1) The major use of meta-programming is that it pre-generates the code at run-time. For example, if we are writing an application and we want a quick lookup table containing keys and values associated with the keys, we can have the program build the table at startup during runtime. | ||
2) It is the key | 2) It is the key ingredient for [http://en.wikipedia.org/wiki/Domain-specific_language Domain Specific Languages]. That is we use meta-programming to encapsulate domain-specific knowledge. | ||
3) Using meta-programming we can easily reduce the cost and time of development for a system by one order of magnitude, while improving the overall quality of the resulting code. Also the maintenance becomes easy. | 3) Using meta-programming we can easily reduce the cost and time of development for a system by one order of magnitude, while improving the overall quality of the resulting code. Also the maintenance becomes easy. | ||
Line 28: | Line 28: | ||
For [http://planet.plt-scheme.org/package-source/dherman/c.plt/3/1/planet-docs/c/index.html Meta programming in C-language], one of the popular examples is the Lex/Yacc system for generation of parsers and lexical scanners. That is, it reads an input stream specifying the lexical analyzer and outputs source code implementing the lexer in C. Instead of doing the laborious work of generating a parser in C, one can use Yacc to generate the repetitive parts of the program. Then, one can add only the code necessary to operate on the parsed entities. | For [http://planet.plt-scheme.org/package-source/dherman/c.plt/3/1/planet-docs/c/index.html Meta programming in C-language], one of the popular examples is the Lex/Yacc system for generation of parsers and lexical scanners. That is, it reads an input stream specifying the lexical analyzer and outputs source code implementing the lexer in C. Instead of doing the laborious work of generating a parser in C, one can use Yacc to generate the repetitive parts of the program. Then, one can add only the code necessary to operate on the parsed entities. | ||
For [http://www.boostpro.com/mplbook/ Meta programming in C++], it is done with C++ templates i.e., we define the program’s content by writing code that generates it. The code is generally written | For [http://www.boostpro.com/mplbook/ Meta programming in C++], it is done with C++ templates i.e., we define the program’s content by writing code that generates it. The code is generally written in between question marks. Usually this code is quite complicated and consists of a template and some instructions on how to fill the code. | ||
Similar programs exist for Java and C#. The general idea, however, is that such an approach can be used anywhere we have a formal specification for which we know how to generate code. | Similar programs exist for Java and C#. The general idea, however, is that such an approach can be used anywhere we have a formal specification for which we know how to generate code. | ||
Line 115: | Line 115: | ||
end | end | ||
Here, the method prod will be defined in the singleton class of | Here, the method prod will be defined in the singleton class of Array (since Array class is considered as an object of Class class). Here we are using the variable self because it always refers to the current object (which is by default the receiver of the method call). So here, self refers to the current object of the class Class i.e., Array. | ||
Now let us look at the use of these singleton classes. The addition of the methods to instances and classes (as discussed above) are some of the uses of singleton classes. Various tricks/methods of Ruby like class_eval, module_eval, instance_variables etc., when invoked on singleton classes will be simple yet powerful in terms of efficiency of implementation of meta-programming, rather than invoking those tricks/methods on self. The following example in which, some of the above mentioned tricks/methods are invoked on a singleton class of an object will illustrate this advantage:<ref>[http://courses.ncsu.edu/csc517//common/lectures/notes/lec12.pdf Lecture Notes on Meta programming]</ref> | Now let us look at the use of these singleton classes. The addition of the methods to instances and classes (as discussed above) are some of the uses of singleton classes. Various tricks/methods of Ruby like class_eval, module_eval, instance_variables etc., when invoked on singleton classes will be simple yet powerful in terms of efficiency of implementation of meta-programming, rather than invoking those tricks/methods on self. The following example in which, some of the above mentioned tricks/methods are invoked on a singleton class of an object will illustrate this advantage:<ref>[http://courses.ncsu.edu/csc517//common/lectures/notes/lec12.pdf Lecture Notes on Meta programming]</ref> | ||
String.class_eval do | |||
class << self | class << self | ||
def | def attribute_reader( *instance_variables ) | ||
instance_variables.each do |instance_variable| | instance_variables.each do |instance_variable| | ||
String.class_eval "def #{instance_variable};@#{instance_variable};end" | |||
end | end | ||
end | end | ||
Line 136: | Line 129: | ||
end | end | ||
Here, we can see that a singleton class is being created for any object. So, we use this class to redefine methods and also to add new methods. For instance, we can add new class | Here, we can see that a singleton class is being created for any object of class String. So, we use this class to redefine methods and also to add new methods. For instance, we can add new instance methods of a class by using the class_eval method (trick) without actually typing the class name explicitly. Also, we can get to know the name of the class the object belongs to by calling the method instance_variable. We can also use techniques like instance_variables to indicate the instance_variables in the class. In this manner we can use all these methods in singleton class and modify methods and variables without actually typing the class name. | ||
=== Method aliasing === | === Method aliasing === | ||
Line 181: | Line 174: | ||
Look at the following example: | Look at the following example: | ||
4.times do | |||
class | class A | ||
def | def method1 | ||
puts " | puts "This is method1" | ||
end | end | ||
end | end | ||
end | end | ||
This code is evaluated at runtime and so | This code is evaluated at runtime and so the definition of the class A is evaluated four times. You might think that it is invalid to define the same class several times. But remember, when Ruby comes across the definition of a class which already have been defined earlier, it just opens the class definition and appends the code in the new definition to the code in the old definition. So, the definition of class A will look like as follows: | ||
class | class A | ||
def | def method1 | ||
puts " | puts "This is method1" | ||
end | end | ||
def | def method1 | ||
puts " | puts "This is method1" | ||
end | end | ||
def | def method1 | ||
puts " | puts "This is method1" | ||
end | |||
def method1 | |||
puts "This is method1" | |||
end | end | ||
end | end | ||
But as you can see the same method is defined | But as you can see the same method is defined four times. Ruby replaces the old definition of a method with the new one every time it comes across a new definition of the same method. So the final definition of class A will look like as follows: | ||
class | class A | ||
def method1 | |||
puts "This is method1" | |||
end | |||
end | end | ||
Now we will look at how to add methods to an existing class at runtime. Suppose we want to define a method which | ==== Adding methods to existing classes and instances ==== | ||
Now we will look at how to add methods to an existing class at runtime. Suppose we want to define a method which returns the array of the square of the elements in an array. It can be done by defining a square method as follows: | |||
def | def square(array) | ||
array.each_with_index do |e i| | arr_square = [] | ||
array.each_with_index do |e, i| | |||
arr_square[i] = e*e | |||
end | end | ||
return arr_square | |||
end | end | ||
But this is not an object-oriented approach as this method has nothing to do with the class in which the | But this is not an object-oriented approach as this method has nothing to do with the class in which the square method is defined. The square method is in fact dealing with the elements of an array. So, the object-oriented approach will be to define the square method in the class Array. | ||
class Array | class Array | ||
def | def square | ||
arr_square = [] | |||
self.each_with_index do |e, i| | self.each_with_index do |e, i| | ||
arr_square[i] = e*e | |||
end | end | ||
return arr_square | |||
end | |||
end | |||
a = [1,2,3,4] | |||
puts a.square.to_s # [1, 4, 9, 16] | |||
Note that if a class already has a built-in method with the name same as new method, then Ruby replaces the built-in method with the new one and so the original implementation of built-in method is lost. | |||
We'll look at some built-in methods which can be used to add methods to existing classes and instances. These are [http://www.ruby-doc.org/core-1.9.2/Module.html#method-i-class_eval class_eval] and [http://www.ruby-doc.org/core-1.9.2/BasicObject.html#method-i-instance_eval instance_eval]. | |||
The class_eval method is used to define instance methods. It is always used in context of a class. Let us look at an example. | |||
class NewClass | |||
end | |||
## Add an instance method to class 'NewClass' | |||
NewClass.class_eval do | |||
def method1 | |||
puts "This is method1" | |||
end | |||
end | |||
NewClass.method1 # NoMethodError | |||
new_obj = NewClass.new | |||
new_obj.method1 # "This is method1" | |||
Here, the method 'method1' is defined as an instance method. It can be invoked on an instance of the class 'NewClass'. | |||
The instance_eval method can be used to define class methods as well as instance methods. When used in context of a class, it generates class methods. When used in context of an instance, it generates instance methods for that particular instance only, those methods will not be generated for other instances of the same class. Let us look at an example. | |||
class NewClass | |||
end | |||
## Adding class method to the class 'NewClass' | |||
NewClass.instance_eval do | |||
def method2 | |||
puts "This is method2" | |||
end | |||
end | |||
NewClass.method2 # "This is method2" | |||
new_obj = NewClass.new | |||
new_obj.method2 # NoMethodError | |||
## Adding instance method to the instance 'new_obj' | |||
new_obj.instance_eval do | |||
def method3 | |||
puts "This is method3" | |||
end | end | ||
end | end | ||
new_obj.method3 # "This is method2" | |||
other_obj = NewClass.method3 | |||
other_obj.method3 # NoMethodError | |||
Here, the method 'method2' can be invoked on the class 'NewClass' but not on it's any instance since it is a class method. The method 'method3' can be invoked on the instance 'new_obj' only and not on the instance 'other_obj'. | |||
==== Defining new classes and attributes at runtime ==== | |||
Now we will look at how a new class and it attributes are defined at runtime. This is useful when the programmer doesn't know what will be the names of the class and its attributes until runtime. | Now we will look at how a new class and it attributes are defined at runtime. This is useful when the programmer doesn't know what will be the names of the class and its attributes until runtime. | ||
Line 272: | Line 323: | ||
If the attribute 'name' is directly accessed, it will give an error as the attribute accessor methods are not defined for the attribute 'name'. | If the attribute 'name' is directly accessed, it will give an error as the attribute accessor methods are not defined for the attribute 'name'. | ||
But, the attribute accessors can be explicitly defined for the attributes using the class_eval method. The [http://www.ruby-doc.org/core-1.9.2/Module.html#method-i-class_eval class_eval] method executes the code between the 'do' and 'end' as if it is written in the static class definition. The class_eval method can also be used to define methods at runtime. | But, the attribute accessors can be explicitly defined for the attributes using the class_eval method. The [http://www.ruby-doc.org/core-1.9.2/Module.html#method-i-class_eval class_eval] method executes the code between the 'do' and 'end' as if it is written in the static class definition. The class_eval method can also be used to define methods at runtime as we saw earlier. | ||
attrs = [attr_name] | attrs = [attr_name] | ||
Line 282: | Line 333: | ||
The attrs is an array which contains all the attributes of the class. The method class_eval executes the line 'attr_accessor *attrs' as if it is defined in the class definition. This causes to define accessor methods for each of the attribute contained in the attrs array. So, the attribute 'name' can be accessed directly. | The attrs is an array which contains all the attributes of the class. The method class_eval executes the line 'attr_accessor *attrs' as if it is defined in the class definition. This causes to define accessor methods for each of the attribute contained in the attrs array. So, the attribute 'name' can be accessed directly. | ||
==== Defining new methods for a class at runtime ==== | |||
Now we will look at how to define new methods for a class at runtime. In Ruby, the methods can be generated dynamically using define_method or using def inside of an eval. | Now we will look at how to define new methods for a class at runtime. In Ruby, the methods can be generated dynamically using define_method or using def inside of an eval. | ||
Line 385: | Line 438: | ||
In the above example, the hook of include is used to modify the class that is including the module. The class is modified to extend the methods of Actions::ClassMethods module. As a result the class Car has access to both the class as well as instance methods. | In the above example, the hook of include is used to modify the class that is including the module. The class is modified to extend the methods of Actions::ClassMethods module. As a result the class Car has access to both the class as well as instance methods. | ||
So, | == Conclusion == | ||
So, we can conclude by saying that various techniques and properties like singleton classes, method aliasing, class definitions and modules help in implementing this simple yet powerful feature called meta-programming efficiently in Ruby. Thus we can also say that Ruby has this powerful feature inbuilt within the language and can implement it with all the various techniques as discussed in this page. | |||
== References == | == References == |
Latest revision as of 13:51, 14 December 2011
In this page, the concept of meta-programming in Ruby is discussed. We first give the definition of meta-programming, along with a brief description. Next, we give an overview of the concept of a meta-program in general i.e., its evolution and implementation in various languages is discussed. We then, discuss about the implementation of the concept of meta-programming in a dynamic and object-oriented programming language called Ruby.
Definitions
A program that manipulates its code or other program’s code as its data is called a meta-program. An established example of meta-program is a compiler. The act of writing these meta-programs, that write or manipulate other programs or themselves as data is called meta-programming. We can also define a meta-program as a program which generates code and writing such programs is called meta-programming.
This allows programmers to get more done in the same amount of time as they would take to write all the code manually, or it gives programs greater flexibility to efficiently handle new situations without recompilation.
The ability of a programming language to be its own meta-language is called reflection or reflexivity. This reflection ability is a feature that is very valuable to facilitate meta-programming.
Uses of Meta-programming
Let us look at some of the uses of meta-programming:<ref>Overview of uses of Meta Programming</ref>
1) The major use of meta-programming is that it pre-generates the code at run-time. For example, if we are writing an application and we want a quick lookup table containing keys and values associated with the keys, we can have the program build the table at startup during runtime.
2) It is the key ingredient for Domain Specific Languages. That is we use meta-programming to encapsulate domain-specific knowledge.
3) Using meta-programming we can easily reduce the cost and time of development for a system by one order of magnitude, while improving the overall quality of the resulting code. Also the maintenance becomes easy.
4) Also, we can use meta-programming for building frameworks and thus perform web application development. Ruby on Rails is one such example. Ruby’s meta-programming feature is the key feature for developing web application in Rails.
5) Lately, meta programming has many advantages in the field of Artificial Intelligence.
Evolution of Meta-programming
Meta-programming was first introduced in Lisp. In Lisp, instead of just programming towards the language, we usually build the language up to our program. This is where the meta-programming comes into picture. For meta-programming purpose, Lisp provides macros as a standard facility to write code that will be directly be read and compiled by the system at run time.
For Meta programming in C-language, one of the popular examples is the Lex/Yacc system for generation of parsers and lexical scanners. That is, it reads an input stream specifying the lexical analyzer and outputs source code implementing the lexer in C. Instead of doing the laborious work of generating a parser in C, one can use Yacc to generate the repetitive parts of the program. Then, one can add only the code necessary to operate on the parsed entities.
For Meta programming in C++, it is done with C++ templates i.e., we define the program’s content by writing code that generates it. The code is generally written in between question marks. Usually this code is quite complicated and consists of a template and some instructions on how to fill the code.
Similar programs exist for Java and C#. The general idea, however, is that such an approach can be used anywhere we have a formal specification for which we know how to generate code. Prolog, a generic purpose programming language, which uses the same data structures as the meta-programming and also provides simple clause expansions, is generally used for meta-programming.
Meta programming in Ruby
In this column, we will discuss about the implementation of meta-programming in a programming language called Ruby. We will also discuss about various techniques which help in implementing meta-programming. Ruby is an object-oriented and dynamic language. Ruby is also reflexive i.e., Ruby program can observe and modify its own structure and behavior at runtime. Also in Ruby, code is nothing but data and data is nothing but code. All these features of Ruby make it implement one of its most powerful features called meta-programming efficiently.
In Ruby, with this meta-programming feature, we can write code that manipulates language constructs like instance variables, classes and modules at runtime. One of the major applications of Ruby is in Rails. Rails is a framework, which can be used for web applications development. The feature of meta-programming in Ruby is the key in building this rails framework.
Open class property of Ruby<ref>Open Classes in Ruby</ref> helps it to enhance the implement of the concept of meta-programming efficiently. In Ruby, the classes (both standard classes and the classes created by us) are never closed i.e., we can always add extra methods to an existing class. All we need to do is open the class to which we want to add additional methods and then define the method we want to add. For instance, consider the following example:
a = [1, 2, 3, 4]
Here ‘a’ is an array and if we want to find the product of all the elements of the array, we need to open the Array class and add the method to calculate the product of the array elements.
class Array def product inject{|a,x| a*x} end end
We can now call method product for the array.
a.product => 24
Generally we add the methods to the classes rather than the class objects, because adding to class objects will make the methods visible and thereby affecting the encapsulation. Sometimes adding a method can override already existing method. So, it is sometimes better to create a subclass to the existing class and add the method to the subclass.
In Ruby, we can show as if there were two methods with same name. With this property, we can implement the concept of meta-programming efficiently in Ruby. Suppose, we have a class called Rectangle and it has an initialize method. Here, we can now instantiate this Rectangle by two ways. That is, either by passing in the coordinates of its corners or by passing in any of its corner along with the length and width of the corner. So, in this manner, even though there is only one initialize method, we can act as if there were two.<ref>Method over-loading in Ruby</ref>
# The Rectangle constructor accepts arguments in either # of the following forms: class Rectangle def initialize(*args) if args.size < 2 || args.size > 3 puts 'Sorry wrong number of arguments passed. This method takes either 2 or 3 arguments.' else puts 'Correct number of arguments passed!!!' if args.size == 2 puts 'Two arguments' else puts 'Three arguments' end end end end Rectangle.new([10, 23], 4, 10) #=> Correct number of arguements passed!!! #=> Three arguements Rectangle.new([10, 23], [14, 13]) #=> Correct number of arguements passed!!! #=> Two arguements
Singleton classes
Singleton classes are one of the major properties of Ruby which make implementation of meta-programming efficient. Singleton class also called as meta-class or anonymous class is a class that acts as a proxy to the objects of a class. So firstly, let us understand what a singleton class means and then we will discuss about how it enhances the implementation of meta-programming in Ruby.
In Ruby, any object will have a class of which it is an instance (the class of the object can be found by calling the method class on it). Ruby has a feature where we can add additional methods to an object. So, when this is done, a new anonymous class is created which acts as an intermediate between the object and its actual class. This intermediate class or proxy class is called as a singleton class. So, whenever the additional method defined for the object is called, the singleton class is searched first and then the actual class is searched (for the implementation). Let us look at the following example to understand this. Consider an array object a defined as follows:<ref>Meta programming in Ruby Overview</ref>
a= [1, 2, 3, 4]
Now, if we want to add a new method to this object which will find out the product of all the elements in the array, we can do it as follows:
class << a def prod inject{|a, x| a*x} end end
Now we can call,
a.prod => 24
Here, when the method prod is called by object a, a singleton class is created between the object and its actual class. This class is hidden and has no name. Also, we cannot instantiate a new object from this hidden anonymous class.
Now, let us discuss about another aspect of singleton classes. As we know all the objects can have a singleton class. Also, any class is actually an object of itself and also of the class Class. That is, the class Array is an object of itself and also of class Class. Suppose that, we define a method called prod just as above for this class Array, that method will actually be executed in the scope of the Array instance of the class Class (since Array is an object of Class class). The following is an example illustrating this:<ref>Singleton classes implementation and its uses in Ruby</ref>
a = [1, 2, 3, 4] class Array class << self def prod inject {|a, x| a*x} end end end
Here, the method prod will be defined in the singleton class of Array (since Array class is considered as an object of Class class). Here we are using the variable self because it always refers to the current object (which is by default the receiver of the method call). So here, self refers to the current object of the class Class i.e., Array.
Now let us look at the use of these singleton classes. The addition of the methods to instances and classes (as discussed above) are some of the uses of singleton classes. Various tricks/methods of Ruby like class_eval, module_eval, instance_variables etc., when invoked on singleton classes will be simple yet powerful in terms of efficiency of implementation of meta-programming, rather than invoking those tricks/methods on self. The following example in which, some of the above mentioned tricks/methods are invoked on a singleton class of an object will illustrate this advantage:<ref>Lecture Notes on Meta programming</ref>
String.class_eval do class << self def attribute_reader( *instance_variables ) instance_variables.each do |instance_variable| String.class_eval "def #{instance_variable};@#{instance_variable};end" end end end end
Here, we can see that a singleton class is being created for any object of class String. So, we use this class to redefine methods and also to add new methods. For instance, we can add new instance methods of a class by using the class_eval method (trick) without actually typing the class name explicitly. Also, we can get to know the name of the class the object belongs to by calling the method instance_variable. We can also use techniques like instance_variables to indicate the instance_variables in the class. In this manner we can use all these methods in singleton class and modify methods and variables without actually typing the class name.
Method aliasing
Method aliasing is a technique used in Ruby to implement the concept of meta-programming. The technique that allows us to give aliases or new names to already existing variables, operators and methods is called as method aliasing. It can also be used to wrap an existing method effectively by intercepting any calls and injecting the desired behavior. Here the new reference or the aliasing name may not be a local, instance, constant, or class variable if we are aliasing a variable.
Using this method or technique we can perform method over riding efficiently and there by change the behavior of an object or a class. Also, we can use aliasing for more expressive options to the programmer using the class. That is, we can provide a second name to a method or variable which can be used outside the class too. There are two keywords in Ruby which provide this method aliasing. They are alias and alias_method. The following is the syntax for both these keywords:<ref>Aliasing representation in Ruby</ref>
alias :new :old alias_method :new, :old
Generally we use alias to give a second name to a method or variable of a class, whereas we use alias_method when we want to assign second name to a method in a Module. So basically, alias keyword takes two arguments as we can see in the syntax. The first argument is the new name or the second name and second argument is the old name or the original name. Generally, we use labels to refer to the methods and the variables for which aliasing is being done. Let us look at an example that illustrates this technique. Consider the following example:
class Array def product inject {|a, x| a*x } end alias :newproduct :product end a = Array.new([1, 2, 3, 4]) a.newproduct #=> 24
In this example, we are defining a new method called product and then aliasing that method with a new name called newproduct in the class Array. Now we can call this method with its alias name as above and still the method will be invoked. This is how method aliasing can be implementing. We can notice here that, this technique can be used to add new methods and alias those methods for a given class. In this manner we can also change the behavior of the class. Also, we can call the aliasing name of a method inside another method thereby implementing method over riding concept.<ref>Method aliasing in Ruby</ref> Consider the following example to illustrate this:
class A def method1 puts ”This is the main method” end alias :method2 :method1 def method1 puts “This is not main method" method2 end end a = A.new() a.method1 => This is not main method => This is main method
So, in this manner method over riding is done using method aliasing. We can thus say that method aliasing can be used to implement the concept of meta-programming. Also, using method aliasing enables Ruby to implement Aspect Oriented Programming and its libraries.
Class Definitions
In statically typed languages, the classes are defined at compile time and used by the program at runtime. But dynamically typed languages do not consider this distinction. In Ruby, the class definitions are expressions and they are evaluated at run time just like any other code. So, the classes and its attributes and even methods can be defined at runtime. Look at the following example:
4.times do class A def method1 puts "This is method1" end end end
This code is evaluated at runtime and so the definition of the class A is evaluated four times. You might think that it is invalid to define the same class several times. But remember, when Ruby comes across the definition of a class which already have been defined earlier, it just opens the class definition and appends the code in the new definition to the code in the old definition. So, the definition of class A will look like as follows:
class A def method1 puts "This is method1" end def method1 puts "This is method1" end def method1 puts "This is method1" end def method1 puts "This is method1" end end
But as you can see the same method is defined four times. Ruby replaces the old definition of a method with the new one every time it comes across a new definition of the same method. So the final definition of class A will look like as follows:
class A def method1 puts "This is method1" end end
Adding methods to existing classes and instances
Now we will look at how to add methods to an existing class at runtime. Suppose we want to define a method which returns the array of the square of the elements in an array. It can be done by defining a square method as follows:
def square(array) arr_square = [] array.each_with_index do |e, i| arr_square[i] = e*e end return arr_square end
But this is not an object-oriented approach as this method has nothing to do with the class in which the square method is defined. The square method is in fact dealing with the elements of an array. So, the object-oriented approach will be to define the square method in the class Array.
class Array def square arr_square = [] self.each_with_index do |e, i| arr_square[i] = e*e end return arr_square end end a = [1,2,3,4] puts a.square.to_s # [1, 4, 9, 16]
Note that if a class already has a built-in method with the name same as new method, then Ruby replaces the built-in method with the new one and so the original implementation of built-in method is lost.
We'll look at some built-in methods which can be used to add methods to existing classes and instances. These are class_eval and instance_eval. The class_eval method is used to define instance methods. It is always used in context of a class. Let us look at an example.
class NewClass end ## Add an instance method to class 'NewClass' NewClass.class_eval do def method1 puts "This is method1" end end NewClass.method1 # NoMethodError new_obj = NewClass.new new_obj.method1 # "This is method1"
Here, the method 'method1' is defined as an instance method. It can be invoked on an instance of the class 'NewClass'.
The instance_eval method can be used to define class methods as well as instance methods. When used in context of a class, it generates class methods. When used in context of an instance, it generates instance methods for that particular instance only, those methods will not be generated for other instances of the same class. Let us look at an example.
class NewClass end ## Adding class method to the class 'NewClass' NewClass.instance_eval do def method2 puts "This is method2" end end NewClass.method2 # "This is method2" new_obj = NewClass.new new_obj.method2 # NoMethodError ## Adding instance method to the instance 'new_obj' new_obj.instance_eval do def method3 puts "This is method3" end end new_obj.method3 # "This is method2" other_obj = NewClass.method3 other_obj.method3 # NoMethodError
Here, the method 'method2' can be invoked on the class 'NewClass' but not on it's any instance since it is a class method. The method 'method3' can be invoked on the instance 'new_obj' only and not on the instance 'other_obj'.
Defining new classes and attributes at runtime
Now we will look at how a new class and it attributes are defined at runtime. This is useful when the programmer doesn't know what will be the names of the class and its attributes until runtime. In Ruby, a class and its attributes are defined statically as follows:
class Xyz attr_accessor :name end
In Ruby, one way to define a class and its attributes at runtime is to use the module_eval method.<ref>Dynamically creating classes</ref> Let us see an example of how the above class can be defined using module_eval method.
class_name = "Xyz" # assume this is inputted by the user attr_name = "name" # assume this is inputted by the user str = ("class #{class_name}; #{attr_name}; end") Object.module_eval(str) # create the class
In the above code, the class_name and attr_name variables contain the class name and the attribute name which are provided by the user at runtime. The module_eval evaluates the string formed for defining the class and creates the class using the value contained by class_name variable and an attribute using the value contained by attr_name variable. It adds the class name as a constant in the program's namespace. An another way to define a class and its attributes at runtime is to use const_set method of class Object.
Let us see an example of how the above class can be defined using const_set method.<ref>How to create a class on the fly in Ruby</ref>
class_name = "Xyz" # assume this is inputted by the user attr_name = "name" # assume this is inputted by the user dynamic_class = Object.const_set(class_name, Class.new) # create the class
Here, the const_set method adds the name "Xyz" in the program’s namespace and make sure it behaves like a normal class as if it is defined using the 'class' keyword.
The class can be instantiated and its attributes can be accessed in the following way.
dynamic_class = Object.const_get(class_name) # get the class inst = dynamic_class.new # instantiate the class puts inst.class # "Xyz" inst.instance_variable_set("@"+attr_name, "John") # set @name to "John" puts inst.instance_variable_get("@"+attr_name) # "John" inst.name = "John" # error puts inst.name # error
The class is instantiated using new method but first the class is retrieved from progam's namespace using the const_get method and the attribute is set and read using instance_variable_set and instance_variable_get methods. These methods require to have the sing '@' before the attribute name unlike the attribute accessors which use symbol. If the attribute 'name' is directly accessed, it will give an error as the attribute accessor methods are not defined for the attribute 'name'.
But, the attribute accessors can be explicitly defined for the attributes using the class_eval method. The class_eval method executes the code between the 'do' and 'end' as if it is written in the static class definition. The class_eval method can also be used to define methods at runtime as we saw earlier.
attrs = [attr_name] dynamic_class.class_eval do attr_accessor *attrs end inst.name = "John" # set name to "John" puts inst.name # "John"
The attrs is an array which contains all the attributes of the class. The method class_eval executes the line 'attr_accessor *attrs' as if it is defined in the class definition. This causes to define accessor methods for each of the attribute contained in the attrs array. So, the attribute 'name' can be accessed directly.
Defining new methods for a class at runtime
Now we will look at how to define new methods for a class at runtime. In Ruby, the methods can be generated dynamically using define_method or using def inside of an eval.
The define_method is a private class method and so it must be invoked from within the class context. Its parameters are the method name which can be either a string, a symbol or a variable and the block which becomes the method body.<ref>Defining dynamic methods</ref>
class Employee ATTRS = [:fname, :lname] def initialize *args @data = Hash.new ATTRS.each_with_index do |attr, i| @data[attr] = args[i] end end ATTRS.each do |attr| define_method "get_#{attr}" do @data[attr.to_sym] end end end e = Employee.new("John", "Smith") puts e.get_fname # "John" puts e.get_lname # "Smith"
This could have been done using def keyword also. But there are two important benefits using define_method. First, we can define the method name dynamically. Second one is that the define_method accepts a block as one parameter which is in fact a closure in Ruby. The another way to define method dynamically is using def inside an eval.
eval %{def get_#{attr} @data["#{attr}".to_sym] end}
Modules
Modules are a way of grouping together methods, constants, classes and other modules in Ruby.<ref>Modules in Ruby</ref> They are defined using the keyword module. The modules cannot be instantiated nor they can be sub classed. But the modules can be added to the other classes so that the classes can make use of the functionality defined in the modules. There are particularly two uses of modules -
They act as namespace and prevent the name clashes that are generally caused by same method names or variable names in different classes. The other use is that they share the functionality between classes. A module can be mixed in a class so that functionality defined in that module becomes available to the class at runtime. Modules serve the purpose of multiple inheritance in Ruby indirectly as multiple modules can be mixed in a class.
Either 'include' or 'extend' can be used to mix in a module’s functionality into a class, 'include' is used to add the module’s methods to the instance of a class, while 'extend' is used to add the module's methods to the class itself.<ref>Include vs Extend in Ruby</ref> One thing it should be noted that only the instance methods of the module are added to the class, no matter either 'include' or 'extend' is used.
module Actions def left puts '"Move to left" end def self.right puts "Move to right" end end
class Car include Actions end Car.new.left # "Move to left" Car.new.right # NoMethodError Car.left # NoMethodError class Truck extend Actions end Truck.left # "Move to left" Truck.right # NoMethodError Truck.new.left # NoMethodError
The module's methods can added to a particular instance of the class also.
class Car end car = Car.new car.extend Actions car.left # "Move to left" car2 = Car.new car2.left # NoMethodError
In the above example, the methods of module 'Actions' are added to the instance 'car' but they are not added to the instance 'car2' as only instance 'car' extends the module 'Actions'.
If you want to add the class methods of a module to a class, then it can be done by making use of Module#included method.<ref>Ruby extend and include</ref>
module Actions def self.included(base) base.extend(ClassMethods) end def left puts "instance method - left" end end module ClassMethods def right puts "class method - right" end end class Car include Actions end
Car.right # "class method - right" Car.new.left # "instance method - left" Car.left # NoMethodError Car.new.right # NoMethodError
In the above example, the hook of include is used to modify the class that is including the module. The class is modified to extend the methods of Actions::ClassMethods module. As a result the class Car has access to both the class as well as instance methods.
Conclusion
So, we can conclude by saying that various techniques and properties like singleton classes, method aliasing, class definitions and modules help in implementing this simple yet powerful feature called meta-programming efficiently in Ruby. Thus we can also say that Ruby has this powerful feature inbuilt within the language and can implement it with all the various techniques as discussed in this page.
References
<references/>
Further reading
1. Implementation of Meta programming in Ruby and its application on a framework called Rails