In this project we have implemented the main set of dynamic languages reflective primitives. Supporting structural reflection allows a wide range of dynamic manipulation of program structures. It also has implications into the object model that these languages use. We will explain these concepts in the following topics.
Reflection is the capability of a computational system to reason about and act upon itself, adjusting itself to changing conditions. The main criterion to categorize runtime reflective systems is taking into consideration what can be reflected. According to that, three levels of reflection can be identified:
Introspection. Program structure can be dynamically consulted but not modified. Both Java and .Net platforms offer this level of reflection.
Structural Reflection. System structure can be modified and the changes are reflected at runtime. An example is the Python feature of adding fields or methods to both objects and classes.
Computational (Behavioral) Reflection. System semantics can be modified, changing the runtime behavior of programs. For instance, MetaXa (formerly called MetaJava) is a Java extension that offers the programmer the ability to dynamically modify the method dispatching mechanism. The most common technique to reach this level of reflection is a Meta-Object Protocol (MOP).
Dynamic languages offer the structural level of reflection in their computational model (this level offers a good trade-off between flexibility and efficiency), offering a higher level of adaptiveness in comparison with "static" languages.
There exist some conceptual inconsistencies between the class-based object-oriented computational model and structural reflection. These inconsistencies were detected and partially solved in the field of object-oriented database management systems. In this area, objects are stored but their structure, or even their types (classes), could be altered afterwards as a result of software evolution.
The first scenario of modifying fields of a class implies updating the structure of every object that is an instance of the modified class. This mechanism was defined as schema evolution in the database world. Dynamic evolution of class methods and fields can produce situations such as accessing fields or methods that do not exist in a specific execution point; these situations are checked by a dynamic type checking mechanism using exception handling, in order to make sure that no incorrect behavior is produced.
There is another situation that a structurally reflective computational model supports, but in this case is much more difficult to model it in a class-based language. How can be modified the structure of an object without altering the rest of its class instances? This problem was detected in the development of MetaXa, a reflective Java platform implementation. In the approach they chose, a new version of the class (called "shadow" class) is created when one of its instances is reflectively modified. This new class is the type of the recently customized object. This model causes different problems such as maintaining the class data consistency, class identity, using class objects in the code, garbage collection, inheritance reliability, and memory consumption, involving a really complex and difficult to manage implementation. One of the conclusions of the MetaXa research project was that the class-based object-oriented model does not fit well in structural reflective environments. They finally stated that the prototype-based model would express reflective features better than class-based ones.
In the prototype-based object-oriented computational model the main abstraction is the object, suppressing the existence of classes. Although this computational model is simpler than the one based on classes, there is no loss of expressiveness; i.e. any class-based program can be translated into the prototype-based model.
For our project, the most important feature of the prototype-based object-oriented computational model is that it models structural reflective primitives in a consistent way. Structural reflective languages such as Moostrap, Self or Kevo have successfully employed it. In this model any object maintains its own structure and even its specialized behavior, so object structure is not constrained by any other model element. Shared behavior could be placed in trait objects, so its customization implies the adaptation of types they are connected to. A prototype-based model offers proper support for reflection primitives with no loss of expressiveness; computational models of dynamic languages are prototype based. A comparison between the different object representations offered by both models is offered in Figure 1.
Similar object behavior (methods of each class in the class-based model) can be represented by trait objects: objects whose members are only methods. Thus, their derived objects share the behavior they define.
Similar object structure (fields of each class in the class-based model) can be represented by prototype objects. This object has a set of initialized fields that represent a common structure.
Copying prototype objects (constructor invocation in the class-based model) is equivalent to the creation of a new instance of a class. A new object with a specific structure and behavior is created by cloning a prototype object.
As an example to clarify the objectives of our project, the following code shows a Python program that uses structural reflection to modify the structure of classes and objects at runtime.
class
Dog:"Constructor"
def
__init__(self, name, age): self.name = name self.age = age"Bark Method"
def
bark(self):"says woof!"
self.age = ageclass
Kennel:"Constructor"
def
__init__(self, owner, color): self.owner = owner self.color = color"getColor method"
def
getColor(self):return
self.color dog = Dog("Brian"
, 5) dog.bark()# Brian says woof!
# Adds a field to a single object
dog.kennel = Kennel(dog.name,"red"
)# Adds a method to a single object
def
getKennel(self):return
self.kennel dog.getKennel = getKennel# red
# Adds a new field to every dog
Dog.breed ="common dog"
# Adds a new method to every dog
def
getBreed(self):return
self.breed Dog.getBreed = getBreed# common dog
We first create a Dog
class with its constructor and the bark
method. An instance is then created (dog
) and a bark
message is passed. Then we modify the structure of a single object adding a new kennel
attribute and its respective getKennel
method, which uses the recently added attribute. Only this dog
will have the particular structure and behavior of having a kennel
. Finally, we add a new attribute to the Dog
class (breed
) and a new getBreed
method to every Dog
instance, modifying the structure and behavior of every dog.
The example code shown previously is represented by the object structure shown in Figure 2.
Dynamic languages use the prototype-based object-oriented model to be capable of offering structural reflection in a coherent way. However, although the so called Common Language Infrastructure (CLI) tries to support a wide set of languages, the .Net platform only offers a class-based object-oriented model optimized to execute "static" languages. Therefore, if we want prototype-based dynamic languages to be interoperable with any existing .Net language or application, we should maintain the class-based model. Our reflective virtual machine should be capable of running any existing .Net application producing the same original behavior.
Taking into account that the CLI virtual machine is a low level platform for executing an ample set of high-level programming languages, both class-based and prototype-based object-oriented models will be supported: the former for running static class-based .Net applications; the latter for executing dynamic reflective programs. .Net compilers could then select services of the appropriate model depending on the language being compiled. Figure 3 shows this scheme.
In our reflective version of Rotor, it is possible to execute any existing .Net language (e.g., C# or J#) application, previously compiled for the original SSCLI. Since we have just extended its computational model, our reflective version of the SSCLI is backward compatible. Existing programs will use the original class-based model of the CLI.
To support different dynamically typed languages that offer structural reflection (e.g., Python or Ruby), we have added structurally reflective primitives with prototype-based semantics. Consequently, any dynamic language compiler will be able to directly use these new services of the reflective platform. It is not necessary to generate extra code for simulating a reflective model over a static one.
There are also programming languages compiled to .Net that use both dynamic and static typing. Two examples are Visual Basic for .Net and Boo. These languages do not support structural reflection; its dynamic typing system is only based on introspection. Dynamic features of these languages will benefit from the performance improvement granted by our reflective virtual machine.
Strongtalk is a Smalltalk modification where static typing is optional, introducing type-checking in a reflective prototype-based object-oriented language without compromising flexibility. A Strongtalk compiler may statically infer types or postpone it to runtime, using both capabilities of our virtual machine.
Our implementation supports structural reflection with both class and prototype-based computational models, implementing static and dynamic typing (performed by the compiler or the virtual machine, respectively).
We have already finished the implementation of structural reflection. We tested the performance of all the reflective primitives. Comparing our system with different Python and Ruby implementations the following are a summary of the results obtained:
Reflective primitives. When evaluating structural reflective primitives, we are 3.317 times faster than CPython and 2.135 times in the case of Ruby. This performance improvement involves a memory consumption increase of 73.2% in comparison with CPython. However, our implementation utilizes 86.67% the memory employed by Ruby. Our platform is also more than 80 and 160 times faster on average than IronPython and Jython respectively.
Real reflective applications. ЯRotor is significantly faster than CPython and IronPython (45% and 122.36% respectively) and much faster (more than 48 times) than Ruby when benchmarking real programs that use both reflective and non-reflective code. For that comparison we used the Parrot benchmark 1.0.4 (a compiler of a subset of Python). Excluding Ruby, the approaches that use a JIT-compiler virtual machine require considerably more memory than the interpreter-based ones: IronPython and ЯRotor use 4.8 and 15.28 times more memory than CPython.
Non-reflective benchmarks. Reflective Rotor executes non-reflective code significantly faster than the rest of implementations. On average, ЯRotor is 2.95, 13.83, 4.36 and 5.06 times faster than CPython, Jython, IronPython and Ruby respectively. Although ЯRotor requires more memory than CPython and Python, these differences are lower than the performance benefit of our implementation: CPython uses 49.58% the memory required by Reflective Rotor. Jython, IronPython and Ruby increase the memory consumption of ЯRotor in 58.87%, 147.36% and 71.63%.
Static code. Finally, our implementation involves an average cost of runtime performance of 12.10% and 4.27% more memory utilization that the SSCLI.