Ruby under a microscope - Method lookup and constant lookup
We can create a module by using the
module keyword, followed by a series of method definitions.
With every new class or module Ruby adds a new scope to a different tree node. Trunk of the tree is the top-level scope, or the beginning of the Ruby code file, where code starts.
Modules are very similar to classes, they are handled differently in three important ways:
- Not able to create objects directly from modules
- Ruby doesn’t allow to specify a superclass for a module
- You can include a module in a class by using include keyword
Modules are Classes - Internally Ruby implements modules as classes. Ruby creates another
RClass/rb_classext_struct structure pair. This structure does not have the
ivptr array, because we can’t create instances of a module.
A Ruby module is a Ruby object that also contains method definitions, a superclass pointer, and a constans table.
Including a module into a class
When we include a module in a class, Ruby creates a copy of the RClass structure for the module and uses it as a superclass for the class. This copy is an included class.
Ruby implements extend in the same way. The included class becomes the supperclas of the target class’s class or metaclass. The extend allows you to add class methods to a class.
Metaclass is the second class created, the hidden class, allows to same any class methods
Method lookup algorithm
When calling a method(sending a message to a receiver) Ruby needs to determine which class implements the method. Sometimes the receiver’s class implements the method. It might be that something other module, class implements the method.
Ruby uses a precise algorithm to search through modules and classes in your program in a particular order to find the target method. Ruby follows the super pointers until it finds the class or the module that contains the target method. It is doing the same for classes and modules.
Essentially there is no difference between including a module and specifying a superclass. Both make new methods available to the target class, booths use the class super pointer internally . Including multiple modules into a Ruby class is equivalent to specifying multiple superclasses. Ruby maintains them in a single list, because of this implementation we have the simple lookup algorithm.
Global Method Cache Method lookup can be time consuming. Ruby caches the result of a lookup for later use. It records which class or module implemented a method in two caches, a global method cache and an inline method cache.
Ruby uses the global method cach to save a mapping between the receiver and the implementer classes.
The inline method cache saves information alongside the compiled YARV instructions that Ruby executes.
The global and inline method caches are valid for a short time. Whenever a new method is created or removed, include a module or perform similar actions, or metaprogramming.
*Including two modules into one class - When we include a module into a class Ruy inserts a copy of the module into the class ancestor chain. When we include a second module, the second module appears first in the ancestor chain and is found first by Ruby’s method lookup.
Including one module into another
Modules don’t allow you to specify superclasses, but we can include one module into another. Modules can’t have superclasses in code, but they can inside Ruby, because Ruby represents modules with classes internally.
Module#prepend - When we use a prepend module, Ruby places it before the class in the superclass chain. When we prepend a module Ruby creates a copy of the target class(called origin class internally) and sets the superclass of the prepended module. Ruby moves all the methods from the original class to the origin class, which means that those methods may now be overridden by ,method with the same name in the prepended module.
Included classes share the method table with the original module. When we include a module Ruby copies the RCLass structure not the underlying method table, it sets the m_tbl in the new copy of the class. This means that modifying the method table by reopening the module and adding new methods will change both the module and the classes which it was already included in.
Constants, like nodules and classes are central to the way Ruby works internally . Whenever a class or a module is defined a constant is defined. Whenever we refer to a clas or a module Ruby has to look up the corresponding constant.
One way Ruby searches for contant is by using the superclass chain.
Lexical scope in Ruby
Lexical scope refers to a section of code within the syntactic structure of your program rather than within the superclass hierarchy or some other scheme. The area between class and end.
To keep the track of the new scope Ruby attaches a couple of pointers to the YARV instructions snippets corresponding to the code it compiles inside the new scope.
Ruby iterates similar to method lookup, here it iterates over the linked list formed by nd_next pointers in each lexical scope while looking for a constant.