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.
R6 namespace
environment is created alongwith R6 package
environment and imports
environment as that happens with any R packages.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
.generator_funs
is loaded with functions: get_inherit
, has_private
, set
, debug
, undebug
and new
. These functions have R6 namespace
as enclosing environment.
assign_func_envs
, get_superclassnames
, list2env2
, create_super_env
, merge_vectors
, get_functions
and get_nonfunctions
functions are encapsulate
d into R6_capsule
environment, it being the enclosing environment for these functions. These functions are bound to R6 namespace
.
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.
Finally, the main function R6Class
is encapsulate
d 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
.
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).R6Class
with R6_capsule
being its enclosing environment. This environment is bound to the execution environment of by name generator
.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.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.
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.
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.
The figure below shows the general logic behind inheritence and overriding.
The private methods/fields are also inherited.
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 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.
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.