I have heard a few times that of the limitations of R is that it is not object oriented (OO). This comment is incorrect in two ways. First of all, R does support object oriented programming using S4 (for a tutorial check A (Not So) Short Introduction to S4 ). It is also incorrect because it assumes that the lack of OO limits a language. The functionality provided by the environments in R has nothing to envy from the OO-paradigm. I give here a brief introduction to using environments in R.
In order to assign and access the value of a variable we have several options. The usual ones are
# To assign a value x <- 5 # To access a value x
But we also have the functions assign
and get
# To assign a value assign("y",10) # To access a value get("y")
A new environment can be created using the function new.env
. At first
glance an environment is like a list, it uses the same notation for
assigning and accessing values
e <- new.env() e$x <- 5 e[["x"]]
But there are differences. For instance we can use get
and assign
with
an environment by using the optional argument envir
:
e2 <- new.env() assign("x",10,envir=e2) get("x",envir=e2)
This does not work with lists. On the other hand, notice that with an environment we cannot use an index to refer to variables.
## with a list l[[1]] # With an environment this raises an error e[[1]]
Now, in the environment e
there is no element y
. Similarly to
lists e$y
returns NULL
, but notice the following interesting
behavior when we use get
get("y",envir=e)
[1] 10
What ?!?!?!?, what is happening?. Is this a bug?.
No. What happens is that in R every variable we define lives in an
environment. And also, every environment has a parent environment
(except the empty environment). And, by default, new.env
assigns the environment from which it is being called as the parent
environment.
When we use get, if the variable we are looking for is not found in
the environment passed to envir
, then it looks for it in
the parent environment and it continues with the same process until it
arrives to the ultimate environment that has no variables called the
empty environment.
It is possible to tell get to not look in the parent environments
passing the argument inherit=F
.
get("y",envir=e,inherit=F)
Notice that this raises an error while e$y
returns NULL
.
When running R, the environment in which one usually works is called the Global environment. The ultimate parent environment is the empty environment, which is an environment with no bindings in it (a binding is an association of a variable name to a value).
Each package that we load into R, also imports an environment. Every
time we start R, the first environment that is loaded is called
the base
environment. Which has many basic
functions. Then every package that is loaded imports an environment
and it's parent is the environment of the previously loaded
package. After the chain of environments coming from imported packages
we have the global environment. To see the chain of environments from
base to global, one can use the function search
.
search()
[1] ".GlobalEnv" "package:shiny" "ESSR" [4] "package:stats" "package:graphics" "package:grDevices" [7] "package:utils" "package:datasets" "package:methods" [10] "Autoloads" "package:base"
If we now import another package and run search again, the environment
of that package will be placed just after =".GlobalEnv"= in the output
of search
.
library(shiny) search()
[1] ".GlobalEnv" "package:shiny" "ESSR" [4] "package:stats" "package:graphics" "package:grDevices" [7] "package:utils" "package:datasets" "package:methods" [10] "Autoloads" "package:base"
The function parent.env
receives an environment as an
argument and it returns it's parent environment. So,
for e
, it returns the global environment (remember that
by default new.env
assigned it as the parent
environment). Now, we can have a different parent environment passing
the optional argument parent
to new.env
. In
the following code a
has the empty environment as a
parent. The empty environment can be accessed with the
function emptyenv
.
a <- new.env(parent=emptyenv()) parent.env(a)
<environment: R_EmptyEnv>
There are other two important aspects of environments that I would
like to mention. One is that unlike lists, environments are hashed
(unless you give the argument hash=F
, see the help of
new.env
). Another one is that when environments are passed as
arguments to functions, they are passed as a reference while lists are
passed as a value. This means that if a list is changed inside a
function, the value outside of the function is not changed; A copy of
the list is created inside the function and the modifications are
applied to the copy without touching the original list.
new.list <- list(v=5) f <- function(x){ x$v <- 6 } f(new.list) new.list$v
[1] 5
We can see that the value of v
in the original list did not
change. On the other hand, if we do the same to an environment the
value of v
inside of the environment does change.
new.en <- new.env() new.en$v <- 5 f(new.en) new.en$v
[1] 6
I finish here my introduction to environments. An exciting follow-up topic is Lexical Scope, about which I will talk in a future post.