Ruby under a microscope - Metaprogramming
Metaprogramming means to program a different or a higher level of abstraction. Ruby allows to change your program to inspect and change itself dynamically.Methods as eval allow your program to write new Ruby code, calling the parser and compiler at the run time.
What is the cost of metaprogramming?
Alternative ways to define methods.
Normal definition process
Class Quote
Def display
Puts “my quote”
end
end
When Ruby executes the class
keyword it creates a new lexical scope for the new Quote
class. Ruby sets the nd_class
pointer in lexical scope to point to an RClass structure for the new Quote class. RClass structure initially has an empty method table. Next Ruby executes the def
keyword. It creates a separate snippet of YARV code for display method when compiling. Later when executing the def
Ruby assigns the code to the target class Quote
saving the name in the method table. When we execute this method Ruby looks up the method using the Method Lookup Algorithm.
There are 3 steps for defining normal methods using def
At compilation it saves a snippet of YARV instructions Uses current lexical scope to obtain a pointer to a class or module, on execution It saves methods name an integer if that maps to the name int the method table for that RClass
Defining class methods using an object prefix
Ruby saves class methods in the metaclass of that class.
class Quote
def self.display
puts "The quick brown fox jumped over the lazy dog."
end
end
Using self
prefix tells Ruby to add the method to the class of the object rather than using current lexical scope.
Ruby uses a different algorithm to save this method.
Evaluates the prefix expression, self is set to Quote
class. Any objects or Ruby expression can be a prefix.
Ruby finds the class of this object
Ruby saves that new method in that class’s methods table. In this case places the display
method in the metaclass for Quote
Defining class methods using a new lexical scope
class Quote
class << self
def display
puts "The quick brown fox jumped over the lazy dog."
end
end
end
class << self
declares a new lexical scope just as class Quote
does. Ruby assigns methods to self’s class.
Ruby evaluates the expression that appears after class <<
in this case self
which evaluates to Quote
class
Ruby finds the class for the object,
Ruby creates a new lexical scope for this class
Now we can use the new lexical scope to define new methods.
Defining methods using Singleton classes
class Quote
end
some_quote = Quote.new
def some_quote.display
puts "The quick brown fox jumped over the lazy dog."
end
Internally Ruby implements this using a hidden class called the singleton class which is like a metaclass for a single object.
A singleton class is a special hidden class that Ruby creates internally to hold methods defined for a particular object.
A metaclass is a singleton class in the case when that object itself is a class.
Defining methods using singleton classes in a lexical scope
class Quote
end
some_quote = Quote.new
class << some_quote
def display
puts "The quick brown fox jumped over the lazy dog."
end
end
end
Difference is that we use some_quote
which evaluates to a single object and not to the class object. Ruby creates a new singleton class for that object along with a new lexical scope. If the singleton class for that object already exist, Ruby will use the same class.
Creating refinements.
This gives us the possibility to define methods and add them later to a class.
module AllCaps
refine Quote do
def display
puts "THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG."
end
end
end
This defines new behaviour for Quote that we can activate later. Refine method creates a new lexical scope. Ruby creates a new refinement module and uses that as the class for this new scope. Ruby saves a pointer to the Quote class in refined_class inside the new refinement module.
Using refinements, activating a refined method. The using
method attaches the refinements from the specified module to the current lexical scope. Ruby 2.0 allows this only in top level lexical scope, might change in future versions.
Quote.new.display
=> The quick brown...
v using AllCaps
w Quote.new.display
=> THE QUICK BROWN…
Metaprogramming and Closures: eval, instance_eval and binding.
Code that writes code
We pass a string to eval and Ruby immediately parses, compiles and executes the code, using the same Bison grammar rules and parse engine. Ruby evaluates the new code string in the same context from where you called eval. Calling eval
invokes the parser and compiler on the text we pass it. When the compiler finishes Ruby creates a new stack frame for use in running the new compiled code, Ruby sets the EP in this new stack frame to point to the lower stack frame. Aside from parsing and compiling the code dynamically eval
works the same way as if we had passed a block to a function. The eval method creates a closure, a combination of a function and the environment where that function was referred
Calling eval with binding
The eval method can take a second parameter a binding, a binding is a closure without a function, it is the referencing environment.
Ruby makes a persistent copy of this environment in the heap because we might call wval long after the current frame has been popped off the stack. The binding object is a n indirect way to access, save and pass around Ruby’s internal rb_env_t structure
instance_eval
The instance_eval method is similar to eval except that it evaluates the given string in the context of the receiver or the object we call it on.
class Quote
def initialize
@str = "The quick brown fox"
end
end
str2 = "jumps over the lazy dog."
obj = Quote.new
obj.instance_eval do
puts "#{@str} #{str2}"
end
The block passed to instance_eval
has 2 environments. The self pointer records the current value of self, indicates which object is the owner of the method Ruby is currently executing. Each level in Ruby call stack can contain different values for self.
Instance_eval changes self to the receiver. When we call instance aval Ruby creates both a closure and a new lexical scope.
Using defined_method
Difference between def nd define_method is that with define_method
we can dynamically construct the method name and we provide the method body as a block, blocks are actually closures, this means that the code inside the new method has access to the environment outside. This is not the case with the def
keyword.