ENESFRITEO
A Brief Introduction to Environments in R

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.

Go back to main page