CSC/ECE 517 Fall 2011/ch4 4g nv
Introduction
Meta-programming in simple terms, is writing of computer programs to write or manipulate other programs (or themselves) as their data, or do that part of the work at compile time that would otherwise be done at runtime. Ruby has open classes which means that, at run time the definition of a class can be changed. All the classes in Ruby are open to be changed by the user at all times. Since, the Ruby class definitions are evaluated at run time, we can enter the world of meta-programming by accomplishing various tasks like compressing the code and so on. It allows how to define methods and classes even during the run-time.
By cleverly planning and applying the techniques of meta-programming, the code can be written efficiently that is DRYer, lighter, more intuitive and more scalable. Few primitive languages that support meta-programming includes Lisp which provides macros as a standard facility to write code that will be directly read and compiled by the system and Prolog which provides simple clause expansions that may be used to generate code as needed. Meta-programming involves thinking about the needs of the system and taking advantage of simpler specifications whenever possible.
Meta-programming in dynamically typed languages
Dynamic programming languages are a class of high-level programming languages that are executed at runtime with many common behaviors that other languages might perform during compilation, if at all. These behaviors could include program extension, by adding new code, by extending objects and definitions, or by modifying the type system, all during program execution. The common examples of dynamic languages are PHP, JavaScript, Perl, Ruby and Python.
Ruby: Ruby is an interpreted scripting language for quick and easy object oriented programming. It is a dynamic, reflective language that has syntax similar to Perl and Python.
Example:
class A; end
A.class_eval do
def foo;
puts 'foo';
end
end
A.foo # => Gives the error saying NoMethodError
A.new.foo # => Gives the output as "foo"
Python: Python is an interpreted, object-oriented language that combined with dynamic typing and dynamic binding.
Metaprogramming in python is supported using Decorators<ref> Meta-programming in Python</ref>.
Example:
Class Ninja(object):
def __init__(self,name) :
self.name = name
drew = Ninja(‘drew’)
adam = Ninja(‘adam’)
Adding a method to the class:
def battle_cry(self)
print ‘%s says zing!!’ % self.name
Ninja.battle_cry = battle_cry
Drew.battle_cry() # => Drew says zing!!
Adam.battle_cry() # => Adam says zing!!
Adding a method to an instance:
import types
def throw_star(self)
print ‘throwing a star’
drew.throw_star = types.MethodType(throw_star,drew)
drew.throw_star() # => throwing a star
drew.__getattribute__(‘battle_cry’)() # => Drew says zing!! {Invoking the method dynamically}
The decorators enhance the original function by adding the functionality of element wise. Now, we can operate on one thing or a series of things which can be applicable efficiently in many dynamic programming situations.
Ways to implement Meta-programming
Introducing the Singleton class
The basic fundamental feature of metaprogramming is Singleton. The Singleton class is the class created for an object if the Ruby interpreter needs to add an instance-specific behavior for that object. All objects in Ruby are open to modification and any particular instance of any class can be changed. Ruby allows us to add a new method to a single object. If there is only one single object that requires addition, alteration or deletion, introducing singleton class is the best way to accomplish this objective. A singleton class is a class whose number of instances that can be instantiated is limited to one. The singleton for class objects is called as Meta-class.
With access to a class object’s meta-class we can then use meta-programming techniques of Ruby to enhance the class<ref>Concept of Singleton class</ref>.
Let us look at the following example:
foobar = Array.new
def foobar.size
"Hello World!"
end
foobar.size # => "Hello World!"
foobar.class # => Array
bizbat = Array.new
bizbat.size # => 0
In the above example, a new object of Array is created and we are adding methods to the object. This way only the object can access the modified method size unlike the other objects of the same class that have access to the method of the original class. This way any number of methods can be added with the singleton class.
The other way to create instance specific behavior to the object in the above example is:
foobar.instance_eval do
def size
"Hello World!"
end
end
foobar.size # => "Hello World!"
foobar.class # => Array
The following is the generic form of creating an instance specific behavior to the newly created object:
class << obj
def hello
'Hello World!'
end
end
The above code creates a singleton class for the object 'obj' with one instance specific method, 'hello' to it.
In Ruby, the Singleton class itself is treated as a receiver. When any method is added to a specific object, Ruby inserts a new anonymous class called a Singleton class into the hierarchy as a container to hold these types of methods. A meta-class is a class whose instance are classes. Just as an ordinary class defines the behavior of certain objects, a meta-class defines the behavior of certain classes and their instances.We can understand two things from this that the instances of meta-classes are classes and that the meta-class defines the behavior of the class. The instances of the class are created using the class 'Class'(e.g., class.new) in Ruby. All classes are instances of Class. Also any class can have its own behavior by defining singleton methods in Ruby. Hence, the meta-classes are defined for Ruby. Further more, not all classes are singleton classes.. Only singleton classes of classes are meta-classes.<ref>Meta-classes</ref>
The following code returns the Object's singleton class.<ref>Singleton Class</ref>:
module Kernel
def singleton_class
class << self
self
end
end unless respond_to?(:singleton_class)
end
Method Aliasing
Aliasing means giving a second name to a method or variable. Method Aliasing is primarily used to override methods and change behavior of classes and objects in order to provide more expressive options to programmers. Ruby provides this functionality with the alias and alias_method keywords. The alias keyword takes two arguments: the old method/variable name and the new method/variable name and create a new name that refers to an existing method, operator, global variable, or regular expression backreference ($&, $`, $', and $+). The method names should be passed as symbols which are immutable as opposed to mutable strings. Aliasing cannot be effectively applied on local variables ,instance variables and constants<ref>Method aliasing</ref>. General syntax for aliasing is:-
alias_attribute(new_name, old_name)
class Objectcount
attr_accessor :count
alias :number :count # alias for getter
def initialize(cnt)
@count = cnt
end
end
a = Objectcount.new(5)
puts a.number
In the following example, start is defined as alias of method begun, and hence object of the class Race can invoke begun method using r.start.
class Race
def begun
puts "Race Started"
end
alias :start :begun
end
r = Race.new
r.start # should refer to r.begun
The behavior of any class can be changed by creating an alias for any method and then creating a new method (with the original method name) that calls the method with the alias. Aliases and methods to individual objects can also be addes using a syntax similar to the inherited class syntax. Following example illustrate a way to override the behavior of some method without changing its original implementation. In the following example, a Tax class is declared and an instance is created. The class declaration uses the alias to alter the behavior of payment method to display the payment type from monthly to yearly.
class Tax
def payment
puts "Monthly Bill"
end
alias_method :expenditure, :payment
def payment
puts "Yearly Bill "
expenditure
end
end
tx = Tax.new
tx.payment
Output:
" Yearly Bill "
" Monthly Bill "
Problem with Aliasing
- Aliases encourage Monkeypatching and thus hold the possiblity of breaking the existing code. Look back at the last section. Adding a alias for String#length breaks libraries that expect the “length” of a string to be its size in bytes. Following can provide a workaround for such issues :-
class String
alias :real_length :length
def length
real_length > 5 ? 'long' : 'short'
end
end
"Hello World".length # => "long"
"Hello World".real_length # => 11
- Loading an Alias twice also disrupt the behaviour and throws exceptions.
Class Definitions
In languages such as C++ and Java, class definitions are processed at compile time which require compiler to load symbol tables, work out storage allocation and constructs dispatch tables. Whereas, in Ruby, class and module definitions are executable code which are parsed at compile time, but created at runtime when they are encountered. This flexiblity allow users to structure programs dynamically. Classes in Ruby are not closed and can thus be extended to add new methods at runtime. Even core system classes like String, Array, and Fixum can be extended just by reopening the class and adding new code<ref>Classes and Methods</ref>. For example :
Redefine existing method:
class String
def length
real_length > 5 ? 'long' : 'short'
end
end
"Hello World".length # => "long"
Add new method:
# reopen the class
String.class_eval do
# define new method
def get_size
# method implementation
length
end
end
A Ruby class is an object of class Class, which contains objects, list of methods and a reference to a superclass. All method calls in Ruby nominate a receiver (self, the current object). Ruby finds the method to invoke by looking at the list of methods in the receiver's class. If it doesn't find the method there, it looks in the superclass, and then in the superclass's superclass, and so on. If the method cannot be found in the receiver's class or any of its ancestors, Ruby invokes the method method_missing on the original receiver.
Instance_eval and Class_eval
There are several versions of evaluation primitives in Ruby. The available contestants are eval, instance_eval, module_eval and class_eval. Firstly, class_eval is an alias for module_eval. Second, there are some differences between eval and the others. Importantly, eval takes only a string to evaluate while the others can evaluate a block instead. The eval involves many complexities and should be the last way to do anything. In most of the cases, we can get away with just evaluating blocks with instance_eval and block_eval.
In a class (or module) definition, the class itself takes the role of the current object self. When you define a method, that method becomes an instance method of the current class. Module#class_eval( ) (also known by its alternate name, module_eval( )) evaluates a block in the context of an existing class. instance_eval( ) only changes self, while class_eval( ) changes both self and the current class. By changing the current class, class_eval( ) effectively reopens the class, just like the class keyword does.
Instance_eval will evaluate the string or the block in the context of the receiver which means that self will be set to the receiver while evaluating. class_eval will evaluate the string or the block in the context of the module it is called on. Instance_eval opens an object that is not a class whereas class_eval() opens a class definition and defines methods with def<ref>Instance_eval and Class_eval</ref>.
Consider the example illustrating class_eval:
class Person
end
Person.class_eval do
def say_hello
"Hello!"
end
end
jimmy = Person.new
jimmy.say_hello # => "Hello!"
Consider the following example illustrating instance_eval:
class Person
end
Person.instance_eval do
def human?
true
end
end
Person.human? # => true
Using define_method
We have seen above how we can create the methods dynamically. One more way of creating the methods dynamically is by using the 'define_method'. This keyword is used to define the methods with the following syntax:<ref>Dynamic method creation</ref>
define_symbol(symbol,method)
where symbol specifies the method name; in spite of its name it can be either symbol, a string, or a variable whose value contains the method name and 'method' is a lambda or Proc object containing the method logic.
Let us consider the following example:
class C
define_method(:hello) do
puts "hello from hello method"
end
end
C.new.hello #=> "hello from hello method"
The above code creates an instance method 'hello' and is called using an object of the class and hence the output "hello from hello method".
Evaluation Table: In the following table, new scope means that code inside the block does not have access to local variables outside of the block. Each object in Ruby has its own metaclass, which is a Class that can have methods, but is only attached to the object itself.
Mechanism | Method Resolution | Method Definition | New Scope |
---|---|---|---|
Class Person | Person | Same | Yes |
Class << Person | Person's metaclass | Same | Yes |
Person.class_eval | Person | Same | No |
Person.instance_eval | Person | Person's metaclass | No |
Using method_missing
When any message is sent to an object, the object executes the first method it finds on its method lookup path with the same name as the message. Method lookup path is nothing but the class in hierarchy to the Object class. If it fails to find any such method, it raises a NoMethodError exception unless a method called method_missing is provided. This method is passed with the symbol of the non-existent method, an array of arguments that were passed in the original call and any block passed to the original method<ref>Method_missing</ref>.
Consider the following example:
lib/widget.rb
class Widget
def method_missing sym, *args
if sym =~ /^(\w+)=$/
instance_variable_set "@#{$1}", args[0]
else
instance_variable_get "@#{sym}"
end
end
end
The following are used for testing the above example:
ruby-1.9.2-p0 > widget = Widget.new
=> #<Widget:0x0000010383f618>
ruby-1.9.2-p0 > widget.name = 'Bob'
=> "Bob"
ruby-1.9.2-p0 > widget.age = 30
=> 30
ruby-1.9.2-p0 > widget.name
=> "Bob"
ruby-1.9.2-p0 > widget.age
=> 30
When a object is called on any method, it first checks in its singleton class. Since, it is not found in this class it searches for the method in the original class to which it is referred and then only it searches for 'method_missing' method in the class. In the above example, a Widget object is created that can have any attributes we want to give it. The 'method_missing' checks if the called method ends with an equal sign, '='. If so, then it assigns the value that is passed to an instance variable with that name. If there is no equal sign, then it tries to get the value of instance variable by that name thus illustrating meta-programming concept in Ruby.;
The method with the name 'a_string' is not declared in the class and hence, it invokes the 'method_missing' method declared in the class and thereby dynamically creating thee . One of the ways Ruby is dynamic is that we can always choose to handle those methods that are called but don't actually exist. Ruby handles such an issue using the 'method_missing' method as illustrated above thus illustrating meta-programming in Ruby.
Singleton methods
Singleton method is a method that is defined only for a single object. Singleton methods helps in customizing specific instance of a class and thus escape from the need of defining another class, which would then only be instantiated once. In ruby we can give any object its own methods<ref> Singleton Methods </ref>.
class SingletonTest
def size
25
end
end
test1 = SingletonTest.new
test2 = SingletonTest.new
def test2.size
10
end
test1.size # => 25
test2.size # => 10
In this example, test1 and test2 belong to same class, but test2 has been given a redefined size method and so they behave differently. A method given only to a single object is called a singleton method.Singleton methods are often used for elements of a graphic user interface (GUI), where different actions need to be taken when different buttons are pressed.
Modules
Modules are a way of grouping methods, classes, and constants. The major benefits using modules is that they provide a namespace and prevent name clashes. They also implement the mixin facility. Ruby allows you to create and modify classes and modules dynamically. Almost any operation can be done on any class or module that isn’t frozen.
Modules have simple access to the method behavior to anyone who wishes to alter but reuse the original behavior. This fact could be applied to both classes that include modules and instances that extend the modules.
Include: Module.include is generally used to mix modules into classes and other modules. Whenever a module is included the constants, methods and the variables of the modules are added to the including module or class instances. This include statement causes all methods in modules to be added as the instance methods and the constants can also be accessed through the method whereas the extend statement adds all the methods as class methods and does nothing with the constants<ref>Extending modules</ref>.
Consider the following example:
module Actions
def left(steps)
Movement.new(self, steps)
end
end
The Actions module can be included to allow a class to generate movements.
class Car
include Actions
end
After Actions is included left can be executed by any instance of Car.
car = Car.new
movement = car.left
Similarly, Object.extend(module, ..) adds the instance methods from each module given as a parameter. However, extend adds the methods to one instance, not to all instances.
car = Car.new
car.extend Actions
movement = car.left
The above code gives car the same behavior as include would, except only the instance that called extend would have this new behavior. Therefore, the above code is valid, but this code would result in an error:
car = Car.new
car.extend Actions
car2 = Car.new
movement = car2.left # calling 'left' here is invalid because car2 does not extend Actions.
Extend is commonly used to mix instance methods of a module into a class. Ruby invokes included or extended methods in the module, if they exist, when the module is included or extended respectively. These have to be defined as class methods - even though this is not a class. Both these methods should take a single parameter; the object or class to which the module is being added<ref>Modules</ref>. Let us consider the following example:
module TestModule
def self.included base
p "I am being included by #{base}"
end
def self.extended base
p "I am being extended by #{base}"
end
end
class TestClass1
include TestModule
end
class TestClass2
extend TestModule
end
p 'Classes now defined'
tc2 = TestClass2.new
tc2.extend TestModule
Output:
"I am being included by TestClass1"
"I am being extended by TestClass2"
"Classes now defined"
"I am being extended by #<TestClass2:0x987a33>”
Other ways to implement Meta-programming
- Introspect on instance variables: A trick that Rails uses to make instance variables from the controller available in the view is to introspect on an objects instance variables. It's easy to do with instance_variables, using instance_variable_get and instance_variable_set. We could copy all the instance variables from one object to another using to accomplish this property.
- Create Procs from blocks and send them around: Materializing a Proc and saving this in variables and sending it around makes many API's very easy to use. This is one of the ways markably used to manage those CSS class definitions.
Advantages of Meta-programming
- It is a new set of conceptual tools that eliminates duplication form your code.
- It allows us to create more expressive APIs than without it and can accomplish some things with significantly less code.
- Ruby can enter into the field of Artificial Intelligence.
- In Real-time, as the analysis is done at runtime any changes to the data model can be reflected in the application at real-time.
Conclusions
Ruby’s way to enhance a class using metaprogramming is seamless, powerful, and built right into the language itself. Metaprogramming helps in keeping code simple and precise. For the most part, the only code that exists in your classes is custom methods to handle specific business rules and other specific one-off customizations.
References
<references/>