Introduction

Last month, I got to know about the most recent version of object oriented system in R (which shows the limitations of the existing OO systems in R), R6 version 2.0.1, which is developed by Winston Chang from RStudio. For past 5 days, I went through the documentation and the source code to understand the system. What I discovered, was the amazingly complicated network of environment system which characterize the class system. In fact, understanding this package will make any novice (like me) understand the concept of environment and non standard evaluation in R. Below, I will describe whatever I could understand of this amazing package.

On loading R6 package

  1. R6 namespace environment is created alongwith R6 package environment and imports environment as that happens with any R packages.

  1. At first, a new environment named R6_capsule is created with enclosing environment as R6 namespace, then a function encapsulate is created, which evaluates any expression in R6_capsule environment. Finally, an empty list generator_funs is created in the R6 namespace.

  1. generator_funs is loaded with functions: get_inherit, has_private, set, debug, undebug and new. These functions have R6 namespace as enclosing environment.

  2. assign_func_envs, get_superclassnames, list2env2, create_super_env, merge_vectors, get_functions and get_nonfunctions functions are encapsulated into R6_capsule environment, it being the enclosing environment for these functions. These functions are bound to R6 namespace.

  3. print.R6, print.R6ClassGenerator, object_summaries, indent and trim functions are loaded in R6 namespace with it being both the binding environment and enclosing environment for these functions. print.R6 and print.R6ClassGenerator functions have bindings in R6 package environment also and are public functions.

  4. Finally, the main function R6Class is encapsulated into R6_capsule environment, it being the enclosing environment for the function. R6Class has binding to R6 namespace and R6 package environment, so that it is visible from Global environment.

On calling function R6Class from Global environment

  1. A new environment opens up (execution environment of R6Class) with Global environment being the calling environment and R6_capsule being the enclosing environment (R6 namespace being its enclosing environment where all the functions of R6 are bound). The arguments to R6Class are evaluated in the calling environment Global environment (following standard evaluation scheme).

  1. A new environment is created from inside the execution environment of R6Class with R6_capsule being its enclosing environment. This environment is bound to the execution environment of by name generator.

  1. New names are bound to generator environment: self, class_name, active, portable, parent_env, lock, inherit, debug_names and class. These are taken from the argument list of R6Class function. self name points to the generator environment. New names public_fields, public_methods, private_fields and private_methods are bound to the generator environment which are lists with appropriate objects and obtained from public and private arguments of R6Class function. Finally, all the functions who were residing in generate_funs list bound in R6 namespace are bound to generator environment and it is also made their enclosing environment. Finally, generator environment is returned with class attribute of R6ClassGenerator and name attribute of paste0(classname, "_generator"). So finally this environment is bound to global environment (calling environment) with R6_capsule as enclosing environment.

On calling new() method for portable class without superclass, inherit and active

A new function execution environment is created with enclosing environment as R6ClassGenerator environment and in maximum cases global environment as the binding and calling environment.

It creates three environments: public_bind_env, private_bind_env and enclos_env. Both public_bind_env and private_bind_env have empty environment as enclosing environment. enclos_env has parent_env as enclosing environment. It has name self binding to public_bind_env and name private binding to private_bind_env. The public_methods and public_fields are bound to the public_bind_env and public_methods are enclosed in enclos_env. Similarly, private_methods and private_fields are bound to the private_bind_env and private_methods are enclosed in enclos_env. Both the public_bind_env and private_bind_env along with their binding names are locked. public_bind_env is given class attribute with class_name and R6 as class names. Finally, public_bind_env is bound to the calling environment and is accesible to the calling environment (global environment). The object also behaves as S3 object, as it has class attribute. Final picture looks like below.

It is interesting to see that the new class has got no connection to the R6_capsule and R6ClassGenerator classes.

Environment structure after calling a public method

As only the public environment is bound to the calling environment, only its methods and fields are visible to the calling environment (which, in most cases, is the global environment). When one of the public methods is called, a new execution environment is produced with enclos_env as the enclosing environment, whose enclosing environment ,in turn, is the parent_env, which was one of the fields of R6ClassGenerator. Other fields and methods in public and private environments are accessed through self and private names which were bound to the respective environments from enclos_env.

So, when writing the functions for a class, we should prefix self$ and private$ for accessing other functions and fields which are present in public and private environments.

On calling new() function with superclass and inherit

R6 classes can handle inheritence which is followed recursively. Inherited classes are called as super classes. They cannot handle multiple inheritence although. Whenever there is inheritence, there must be mechanisms for inheritence of fields and methods and mechanisms for overriding them. The already complicated environment structure gets more complicated when inheritence is implemented.

Mechanisms of inheritence and overriding

The figure below shows the general logic behind inheritence and overriding.

The private methods/fields are also inherited.

Environment structure after running new()

As with object creation without super class, initially three environments are formed: public, private and enclos_env. Both public and private environments are enclosed by empty environment and enclos_env is enclosed by the parent_env of the class. enclos_env contain two names self and private which bind to public and private environments. In addition to them, two environments, super_bind (bound to enclos_env by super name) and super_enclos environments are produced. super_enclos environment contains self and private names which bind to public and private environments. super_bind environment is enclosed by empty_env and super_enclos is enclosed by parent_env of the super class.

Public and private fields of the super class and the child class are combined as per the schema described above and are bound to public and private environments (super_bind environment does not contain any fields). Public and private methods of super class are enclosed in super_enclos environment and similarly, public and private methods of child class are enclosed in enclos_env. Public and private overridden methods and methods defined in child class are bound to respective methods from public and private environments respectively. Finally, public environment is bound to the calling environment (usually, global environment) and a class attribute (class_name of child class, class_name of super class and R6) is added.

On calling any of the public methods

On calling a new method, a new execution environment is produced with enclos_env or super_enclos environment being the enclosing environment. The public and private environments are accessed with self and private names present in both enclos_env and super_enclos environments. Methods present only in super class (methods which are over-ridden by the child class) are accessed from enclos_env by super name.

Conclusions

Rest of the concepts and usage can be checked from the package vignettes. I have not discussed about the environment structure for non portable classes. The package is under active development and underlying structure may change in future.