Environments
More unanswered questions.
Environment basics
- List three ways in which an environment differs from a list.
- Every name in an environment is unique.
- The names in an environment are not ordered (i.e., it doesn’t make sense to ask what the first element of an environment is).
- An environment has a parent.
- Environments have reference semantics.
- If you don’t supply an explicit environment, where do
ls()
andrm()
look? Where does<-
make bindings?
ls()
will list all parents of the global environment. rm()
will remove the elements from current environment.
<-
makes bindings in current enviroment(.GlobalEnv
)
- Using
parent.env()
and a loop (or a recursive function), verify that the ancestors ofglobalenv()
includebaseenv()
andemptyenv()
. Use the same basic idea to implement your own version ofsearch()
.
env <- .GlobalEnv
while(!identical(env, emptyenv())){
env <- parent.env(env)
if(identical(env, baseenv()) | identical(env, emptyenv()))
print(env)
}
## <environment: base>
## <environment: R_EmptyEnv>
search_new <- function(env = .GlobalEnv){
results <- NULL
while(!identical(env, emptyenv())){
results <- c(environmentName(env), results)
env <- parent.env(env)
}
results
}
Recursing over environments
- Modify
where()
to find all environments that contain a binding for name.
Not sure
where <- function(name, env = parent.frame()) {
{
stopifnot(is.character(name), length(name) == 1)
env <- as.envi(env)
if (identical(env, emptyenv())) {
stop("Can't find ", name, call. = FALSE)
}
if (exists(name, env, inherits = FALSE)) {
env
}
else {
where(name, parent.env(env))
}
}
}
- Write your own version of
get()
using a function written in the style ofwhere()
.
get_new <- function(name, env = parent.frame()) {
{
stopifnot(is.character(name), length(name) == 1)
env <- as.environment(env)
if (identical(env, emptyenv())) {
stop("Can't find ", name, call. = FALSE)
}
if (exists(name, env, inherits = FALSE)) {
env[[name]]
}
else {
get_new(name, parent.env(env))
}
}
}
- Write a function called
fget()
that finds only function objects. It should have two arguments, name and env, and should obey the regular scoping rules for functions: if there’s an object with a matching name that’s not a function, look in the parent. For an added challenge, also add an inherits argument which controls whether the function recurses up the parents or only looks in one environment.
fget_new <- function(name, env = parent.frame()) {
{
stopifnot(is.character(name), length(name) == 1)
env <- as.environment(env)
if (identical(env, emptyenv())) {
stop("Can't find ", name, call. = FALSE)
}
if (exists(name, env, inherits = FALSE) & is.function(env[[name]])) {
env[[name]]
}
else {
fget_new(name, parent.env(env))
}
}
}
- Write your own version of
exists(inherits = FALSE)
(Hint: usels()
.) Write a recursive version that behaves like exists(inherits = TRUE).
exists_new <- function(name, env = parent.frame()){
stopifnot(is.character(name), length(name) == 1)
env <- as.environment(env)
name %in% ls(envir = env)
}
exists_inherit <- function(name, env = parent.frame()){
if (identical(env, emptyenv())) {
stop("Can't find ", name, call. = FALSE)
}
if ( name %in% ls(envir = env)) {
TRUE
}
else {
exists_inherit(name, parent.env(env))
}
}
Function environments
- List the four environments associated with a function. What does each one do? Why is the distinction between enclosing and binding environments particularly important?
enclosing environment is used for lexical scoping and determines how the function finds values;
binding environment of a function are all the environments which have a binding to it. I determines how we find the function.
execution environment: Calling a function creates an ephemeral execution environment that stores variables created during execution.
calling environment: Every execution environment is associated with a calling environment, which tells you where the function was called.
The distinction between enclosing and binding environments is important because package namespaces keep packages independent
Binding names to values
- What does this function do? How does it differ from <<- and why might you prefer it?
This function looks for the variable name in the environment. If it exists, it will modifiy it to 10.
rebind <- function(name, value, env = parent.frame()) {
if (identical(env, emptyenv())) {
stop("Can't find ", name, call. = FALSE)
} else if (exists(name, envir = env, inherits = FALSE)) {
assign(name, value, envir = env)
} else {
rebind(name, value, parent.env(env))
}
}
a <- 10
rebind("a", 10)
- Create a version of
assign()
that will only bind new names, never re-bind old names. Some programming languages only do this, and are known as single assignment languages.
rebind <- function(name, value, env = parent.frame()) {
if (identical(env, emptyenv())) {
stop("Can't find ", name, call. = FALSE)
} else if (exists(name, envir = env, inherits = FALSE) &
get(name) == value ) {
stop(name, " already exists", call. = FALSE)
} else if(exists(name, envir = env, inherits = FALSE)) {
assign(name, value, envir = env)
} else {
rebind(name, value, parent.env(env))
}
}
b <- 3
rebind("b", 1)
- Write an assignment function that can do active, delayed, and locked bindings. What might you call it? What arguments should it take? Can you guess which sort of assignment it should do based on the input?