Читать книгу The Big R-Book - Philippe J. S. De Brouwer - Страница 185
Оглавление♣5♣ Lexical Scoping and Environments
5.1 Environments in R
Environments can be thought of as a set of existing or declared objects (functions, variables, etc.). When we start R, it will create an environment before the command prompt is available to the user.
The top-level environment is the R command prompt. This is the “global environment” and known as R_GlobalEnv
and can be accessed as .GlobalEnv
.
global environment
R_GlobalEnv
As mentioned earlier, the function ls()
shows which variables and functions are defined in the current environment. The function environment()
will tell us which environment is the current one.
environment()
environment() # get the environment ## <environment: R_GlobalEnv> rm(list=ls()) # clear the environment ls() # list all objects ## character(0) a <- “a” f <- function (x) print(x) ls() # note that x is not part of.GlobalEnv ## [1] “a” “f”
When a function starts to be executed this will create a new environment that is subordonated to the environment that calls the function.
# f # Multiple actions and side effects to illustrate environments # Arguments: # x -- single type f <- function(x){ # define a local function g() within f() g <- function(y){ b <- “local variable in g()” print(” -- function g() -- “) print(environment()) print(ls()) print(paste(“b is”, b)) print(paste(“c is”, c)) } # actions in function f: a <<- ‘new value for a, also in Global_env’ x <- ‘new value for x’ b <- d # d is taken from the environment higher c <- “only defined in f(), not in g()” g(“parameter to g”) print(” -- function f() -- “) print(environment()) print(ls()) print(paste(“a is”, a)) print(paste(“b is”, b)) print(paste(“c is”, c)) print(paste(“x is”, x)) } # Test the function f(): b <- a <- c <- d <- pi rm(x) ## Warning in rm(x): object ‘x’ not found f(a) ## [1] “ -- function g() -- “ ## <environment: 0x557538bf9e28> ## [1] “b” “y” ## [1] “b is local variable in g()” ## [1] “c is only defined in f(), not in g()” ## [1] “ -- function f() -- “ ## <environment: 0x557536bcc808> ## [1] “b” “c” “g” “x” ## [1] “a is new value for a, also in Global_env” ## [1] “b is 3.14159265358979” ## [1] “c is only defined in f(), not in g()” ## [1] “x is new value for x” # Check the existence and values: a ## [1] “new value for a, also in Global_env” b ## [1] 0 c ## [1] 3.141593 x ## Error in eval(expr, envir, enclos): object ‘x’ not found
Each function within a function or environment will create a new environment that has its own variables. Variable names can be the same but the local environment will always take precedence. A few things stand out in the example above.
The variable a does not appear in the scope of the functions.
However, a function can access a variable defined the level higher if it is not re-defined in the function itself (see what happens with the variable c: it is not defined in g(), so automatically R will search the environment above.
The function g() can use the variable b without defining it or without it being passed as an argument. When it changes that variable, it can use that new value, but once we are back in the function f(), the old value is still there.
5.2 Lexical Scoping in R
Just as any programming language, R as rules for lexical scoping. R is extremely flexible and this can be quite intimidating when starting, but it is possible to amass this flexibility.
First, a variable does not necessarily need to be declared,R will silently create it or even change it is type.
x <- ‘Philippe’ rm(x) # make sure the definition is removed x # x is indeed not there (generates an error message) ## Error in eval(expr, envir, enclos): object ‘x’ not found x <- 2L # now x is created as a long integer x <- pi # coerced to double (real) x <- c(LETTERS[1:5]) # now it is a vector of strings
This can, of course, lead to mistakes in our code: we do not have to declare variables, so we cannot group those declarations so that these error become obvious. This means that if there is a mistake, one might expect to see strange results that are hard to explain. In such case, debugging is not easy. However, this is quite unlikely to come into your way. Follow the rule that one function is never longer than half an A4 page and most likely this feature of R will save time instead of increasing debugging time.
Next, one will expect that each variable has a scope.
# f # Demonstrates the scope of variables f <- function() { a <- pi # define local variable print(a) # print the local variable print(b) # b is not in the scope of the function } # Define two variables a and b a <- 1 b <- 2 # Run the function and note that it knows both a and b. # For b it cannot find a local definition and hence # uses the definition of the higher level of scope. f() ## [1] 3.141593 ## [1] 2 # f() did not change the value of a in the environment that called f(): print(a) ## [1] 1
This illustrates that the scoping model in R is dynamical scoping. This means that when a variable is used, that R in the first place will try to find it in the local scope, if that fails, it goes a step up and continues to do so until R ends up at the root level or finds a definition.
dynamical scoping
Note – Dynamic scoping
Dynamic scoping is possible, because R is an interpreted language. If R would be compiled, then the compiler would have a hard time to create all possible memory allocations at the time of compilation.
To take this a step further we will study how the scoping within S3 objects works.1 The example below is provided by the R Core Team.
# Citation from the R documentation: # Copyright (C) 1997-8 The R Core Team open.account <- function(total) { list( deposit = function(amount) { if(amount <= ) stop(“Deposits must be positive!\n”) total <<- total + amount cat(amount,“deposited. Your balance is”, total, “\n\n”) }, withdraw = function(amount) { if(amount > total) stop(“You do not have that much money!\n”) total <<- total - amount cat(amount,“withdrawn. Your balance is”, total, “\n\n”) }, balance = function() { cat(“Your balance is”, total, “\n\n”) } ) } ross <- open.account(100) robert <- open.account(200) ross$withdraw(30) ## 30 withdrawn. Your balance is 70 ross$balance() ## Your balance is 70 robert$balance() ## Your balance is 200 ross$deposit(50) ## 50 deposited. Your balance is 120 ross$balance() ## Your balance is 120 try(ross$withdraw(500)) # no way.. ## Error in ross$withdraw(500) : You do not have that much money!
This is a prime example of how flexible R is. At first this is quite bizarre, until we notice the <<-
operator. This operator indicates that the definition of a level higher is to be used. Also the variable passed to the function automatically becomes an attribute of the object. Or maybe it was there because the object is actually defined as a function itself and that function got the parameter “amount” as a variable.
This example is best understood by realizing that this can also be written with a declared value “total” at the top level of our object.
Warning – Dynamical scoping
While dynamical scoping has its advantages when working interactively, it makes code more confusing, and harder to read and maintain. It is best to be very clear with what is intended (if an object has an attribute, then declare it).Also never use variables of a higher level, rather pass them to your function as a parameter, then it is clear what is intended.)
In fact, a lot is going on in this short example and especially in the line total <<- total + amount
. First, of all, open.account
is defined as a function. That function has only one line of code in some sense. This is a fortiori the last line of code, and hence, this is what the function will return. So it will try to return a list of three functions: “deposit,” “withdraw” and “balance.”
What might be confusing in the way this example is constructed is probably is that when the function open.account is invoked, it will immediately execute the first function of that list. Well, that is not what happens. What happens is that the open.account
object gets a variable total
, because it is passed as an argument. So, it will exist within the scope of that function.
The odd thing is that rather than behaving as a function this construct behaves like a normal object that has an attribute amount
and a creator function open.account()
. This function in sets this attribute to be equal to the value passed to that function.
Hint –Write readable code
R will store variable definitions in its memory, and it may happen that you manage to read in a file for example, but then keep a mistake in your file. If you do not notice the mistake, it will go unnoticed (since your values are still there and you can work with them). The next person who tries to reproduce your code will fail. Start each file that contains your code with:
rm(list=ls()) # clear the environment
At this point, it is important to get a better understanding of the OO model implemented in R – or rather the multitude of models that are implemented in R.
Note
1 1 To fully appreciate what is going on here, it is best to read the section on the object models (Chapter 6 “The Implementation of OO” on page 87) in R first and more especially Section 6.2 “S3 Objects” on page 91.