CSC/ECE 517 Fall 2011/ch4 4g nv

From Expertiza_Wiki
Jump to navigation Jump to search

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. Most dynamic languages are dynamically typed, but not all are among which Ruby is the best example.

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.

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. The singleton meta-class can be accessed explicitly using the following<ref>Singleton Class</ref>:

module Kernel
 def singleton_class
  class << self
   self
  end
 end
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

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:

  class Dummy 
   def method_missing(m, *args, &block)  
     puts "There's no method called #{m} here -- please try again." 
   end  
  end  

 Dummy.new.anything  

 The output is:
    There's no method called anything here -- please try again.   

In the above example, since there is no method named anything in the Dummy class, it searches and calls the method_missing in the class and executes.

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/>

See Also