I've defined a list of anonymous functions which use a variable defined in an outer scope.
funclist <- list()
for(i in 1:5)
{
funclist[[i]] <- function(x) print(i)
}
funclist[[1]]('foo')
The output is:
[1] 5
It seems that i is captured by reference. I'd like it to be captured by value, i.e. the output should be
[1] 1
Is there a way to tell R to capture i by value rather than by reference?
When you run a for loop, this creates a variable in the environment the loop is run in, and functions created in the loop are also run from this environment. So whenever you run the functions created in this way that use the index value from the loop, they only have access to the final value, and only as long as that varaible remains (try rm(i) and attempting to fun one of the functions in the list).
What you need to do is bind the index value to the function in their own environment. lapply will do this for you automatically. However, there is a gotcha with lazy evaluation. What you have to do is also force the evaluation of i before creating the anonymous function:
funclist <- lapply(1:5, function(i) {force(i); function(x) print(i)})
funclist[[1]]('foo')
[1] 1
funclist[[5]]('foo')
[1] 5
My read on what you want is to store a value inside the function's environment when the function is defined, and then hold onto that value for internal computations.
For that, you want a closure:
i <- 3
test <- local({
i <- i
function(x) x[i]
})
test(letters[1:5]) # returns 'c'
i <- 5
test(letters[1:5]) # still returns 'c' (i.e. i is local to the test closure)
Is that what you wanted?
Related
I have the function below.
test_fun <- function() {
a <- 1
b <- 2
}
Is there a way I run this function and a and b will be assigned in the parent environment (in this case globalenv)? I don't want to modify the function (no assign with envir or <<-), but call it in a way that what I want will be achieved.
A better pattern to use, from the point of encapsulation, might be to have your custom function return a 2D vector containing the a and b values:
test_fun <- function() {
a <- 1
b <- 2
return(c(a, b))
}
result <- test_fun()
a <- result[1]
b <- result[2]
The preferred method for passing information/work done in a function is via the return value, rather than trying to manage the issue of different scopes.
Normally R functions return their outputs. Functions such as test_fun
are discouraged but if you want to do it anyways use trace as shown below. This will cause the code given in the exit argument to run at function exit. No packages are used.
trace("test_fun", exit = quote(list2env(mget(ls()), globalenv())), print = FALSE)
test_fun()
a;b
## [1] 1
## [1] 2
Alternatives
Some alternatives for the exit= argument are the following.
(a) below is similar to above except that rather than leaving the objects loose in the global environment it first creates an environment env in the global environment and puts them there.
In (b) below, the objects are copied to environment(test_fun) which is the environment in which test_fun is defined. (If test_fun is defined in the global environment then it gives the same result as the code at the top of the answer.)
In (c) below, the parent frame of test_fun is the environment of the caller of test_fun and can vary from one call to another if called from different places. (If called from the global environment then it gives the same result as the code at the top of the answer.)
It would also be possible to add the current frame within test_fun to the search path using attach but there are a number of gotchas associated with that so it is not shown.
# (a) copy objects to environment env located in global environment
assign("env", new.env(), globalenv())
trace("test_fun", exit = quote(list2env(mget(ls()), env)), print = FALSE)
# (b) copy objects to environment in which test_fun is defined
trace("test_fun",
exit = quote(list2env(mget(ls()), environment(test_fun)), print = FALSE)
# (c) copy object to parent.frame of test_fun; 5 needed as trace adds layers
trace("test_fun", exit = quote(list2env(mget(ls()), parent.frame(5))),
print = FALSE)
The title is a bit loose. I would like to create a function that takes a list name as a variable, and assigns elements to that list. So far, I can access the list's elements via its name in the my_fun function. I can then modify the list within the function (using the function parameter), and print to verify that the assignment has worked. Of course, I'm not saving any of this to the global environment, so when I call the list outside of the function, it hasn't changed.
my_list <- list('original_element')
my_fun <- function(some_list) {
# check that I can access the list using the parameter name
print(some_list[[1]])
# modify the list
some_list[[1]] <- 'element_assigned_in_function'
# check that the list was modified
print(some_list[[1]])
}
my_fun(my_list)
# [1] "original_element"
# [1] "element_assigned_in_function"
# of course, my_list hasn't been modified in the global environment
my_list
# [[1]]
# [1] "original_element"
My question is, how could you assign new elements to this list within the function and store them in the global environment? If I simply try to make the assignment global with <<-, the following error is thrown:
Error in some_list[[1]] <<- "element_assigned_in_function" :
object 'some_list' not found
I've tried to use assign and set envir = .GlobalEnv, but that doesn't work and I don't think assign can be used with list elements. I suspect that there may be some quoting/unquoting/use of expressions to get this to work, but I haven't been able to figure it out. Thank you.
First of all, I'll be clear that I do not encourage assigning values to variables from inside the function in global environment. Ideally, you should always return the value from the function which you want to change. However, just for demonstration purpose here is a way in which you can change the contents of the list from inside the function
my_fun <- function(some_list) {
list_name <- deparse(substitute(some_list))
some_list[[1]] <- 'element_assigned_in_function'
assign(list_name, some_list, .GlobalEnv)
}
my_list <- list('original_element')
my_fun(my_list)
my_list
#[[1]]
#[1] "element_assigned_in_function"
If I make an environment with a list in it, and want to assign values to that list, why does the following fail when using get and assign?
res <- new.env()
res$calls <- vector("list", 100)
res$counter <- 1
## works fine
res$calls[[1]] <- 1
## Fails, why?
get("calls", envir=res)[[get("counter", envir=res)]] <- 2
## doesnt make the assignment
val <- get("calls", envir=res)[[get("counter", envir=res)]]
assign("val", 2, envir=res)
I think the following will address your issue:
get("calls", envir=res)[[get("counter", envir=res)]] <- 2 fails because get is not a replacement function. On the other hand res$calls[[1]] <- 1 is actually a replacement function which you can see if you type help('[[<-'). This is the function used when you make an assignment. I think the reason why get has no replacement counterpart i.e. (get<-) is that there is a specific function to do this, which is called assign (as per #TheTime 's comment).
For the second case val <- get("calls", envir=res)[[get("counter", envir=res)]] is created in the global environment. When you use assign("val", 2, envir=res) a res$val variable is created inside the res environment which you can see below:
> res$val
[1] 2
However, val remains the same on the global environment as 1:
> val
[1] 1
So, You probably won't be able to do the assignment with either get or assign. get won't allow it because it is not a replacement function and ?assign mentions:
assign does not dispatch assignment methods, so it cannot be used to set elements of vectors, names, attributes, etc.
So, you can just use the normal [[<- assignment method. #Frank in the comments provides a nice way like:
res[[ "calls" ]][[ res[["counter"]] ]] <- 2
I have a list containing 18 elements called bx2.
I want to use bx2 in a function,
XlsMaker <- function(x) {
library("XLConnect")
a <- length(x)
b <- paste0(x,".xlsx")
for (i in 1:a){
writeWorksheetToFile(b, data = x[[i]], sheet = names(x[i]))
}
}
but when put I bx2 into the function it pulls in all the elements of the list rather than just the name of the list.
Is it possible to re-write the function so that b becomes bx2.xlsx?
The line b <- paste0(x,".xlsx") is wrong. That calls paste0 on the object x itself which is not at all what you want to do. You want to call it on the name of the object.
This in general opens a can of worms because objects can have two different names in two different places. Consider: the object named bx2 in your global environment is now named x within the function's scope. If you only want to call this function from the top level (e.g. for interactive use), you can safely get the name of the object from the parent environment (the environment you called the function from) by replacing that line with:
x_name <- deparse(substitute(x))
b <- paste0(x_name, ".xlsx")
The substitute function gets the name of x in the parent environment, as a special name object. The deparse function converts this name into a character vector of length one.
The reason I said this is only safe to use at the top level is that substitute can return surprising or unintended results if not used carefully. Hadley Wickham goes into detail on this point in his book.
I think you just want to deparse the parameter name
XlsMaker <- function(x) {
varname <- deparse(substitute(x))
library("XLConnect")
a <- length(x)
b <- paste0(varname ,".xlsx")
for (i in 1:a){
writeWorksheetToFile(b, data = x[[i]], sheet = names(x[i]))
}
}
bx2 <-list(1:3, 4:6)
XlsMaker(bx2)
I have a function in R that structures my raw data. I create a dataframe called output and then want to make a dynamic variable name depending on the function value block.
The output object does contain a dataframe as I want, and to rename it dynamically, at the end of the function I do this (within the function):
a = assign(paste("output", block, sep=""), output)
... but after running the function there is no object output1 (if block = 1). I simply cannot retrieve the output object, neither merely output nor the dynamic output1 version.
I tried this then:
a = assign(paste("output", block, sep=""), output)
return(a)
... but still - no success.
How can I retrieve the dynamic output variable? Where is my mistake?
Environments.
assign will by default create a variable in the environment in which it's called. Read about environments here: http://adv-r.had.co.nz/Environments.html
I assume you're doing something like:
foo <- function(x){ assign("b", x); b}
If you run foo(5), you'll see it returns 5 as expected (implying that b was created successfully somewhere), but b won't exist in your current environment.
If, however, you do something like this
foo <- function(x){ assign("b", x, envir=parent.frame()); b}
Here, you're assigning not to the current environment at the time assign is called (which happens to be foo's environment). Instead, you're assigning into the parent environment (which, since you're calling this function directly, will be your environment).
All this complexity should reveal to you that this will be fairly complex, a nightmare to maintain, and a really bad idea from a maintenance perspective. You'd surely be better off with something like:
foo <- function(x) { return(x) };
b <- foo(5)
Or if you need multiple items returned:
foo <- function(x) { return(list(df=data.frame(col1=x), b=x)) }
results <- foo(5)
df <- results$df
b <- results$b
But ours is not to reason why...