Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enclosing variables within for loop

So consider the following chunk of code which does not work as most people might expect it to

#cartoon example
a <- c(3,7,11)
f <- list()

#manual initialization
f[[1]]<-function(x) a[1]+x
f[[2]]<-function(x) a[2]+x
f[[3]]<-function(x) a[3]+x

#desired result for the rest of the examples
f[[1]](1)
# [1] 4
f[[3]](1)
# [1] 12

#attempted automation
for(i in 1:3) {
   f[[i]] <- function(x) a[i]+x
}

f[[1]](1)
# [1] 12
f[[3]](1)
# [1] 12

Note that we get 12 both times after we attempt to "automate". The problem is, of course, that i isn't being enclosed in the function's private environment. All the functions refer to the same i in the global environment (which can only have one value) since a for loop does not seem to create different environment for each iteration.

sapply(f, environment)
# [[1]]
# <environment: R_GlobalEnv>
# [[2]]
# <environment: R_GlobalEnv>
# [[3]]
# <environment: R_GlobalEnv>

So I though I could get around with with the use of local() and force() to capture the i value

for(i in 1:3) {
   f[[i]] <- local({force(i); function(x) a[i]+x})
}

f[[1]](1)
# [1] 12
f[[3]](1)
# [1] 12

but this still doesn't work. I can see they all have different environments (via sapply(f, environment)) however they appear to be empty (ls.str(envir=environment(f[[1]]))). Compare this to

for(i in 1:3) {
   f[[i]] <- local({ai<-i; function(x) a[ai]+x})
}

f[[1]](1)
# [1] 4
f[[3]](1)
# [1] 12

ls.str(envir=environment(f[[1]]))
# ai :  int 1
ls.str(envir=environment(f[[3]]))
# ai :  int 3

So clearly the force() isn't working like I was expecting. I was assuming it would capture the current value of i into the current environment. It is useful in cases like

#bad
f <- lapply(1:3, function(i) function(x) a[i]+x)
#good
f <- lapply(1:3, function(i) {force(i); function(x) a[i]+x})

where i is passed as a parameter/promise, but this must not be what's happening in the for-loop.

So my question is: is possible to create this list of functions without local() and variable renaming? Is there a more appropriate function than force() that will capture the value of a variable from a parent frame into the local/current environment?

like image 799
MrFlick Avatar asked Sep 26 '14 16:09

MrFlick


People also ask

Can you put a for loop inside a variable?

Yes definitely you can use same name variable.

Can you have a for loop within a for loop?

A nested loop has one loop inside of another. These are typically used for working with two dimensions such as printing stars in rows and columns as shown below. When a loop is nested inside another loop, the inner loop runs many times inside the outer loop.

What is the scope of variable in for loop?

The variable is within the scope of the loop. I.e. you need to be within the loop to access it. It's the same as if you declared a variable within a function, only things in the function have access to it.

Can we use variable outside for loop?

If the variable is declared outside the loop, then it has the global scope as it can be used through-out the function and inside of the loop too. If the variable is declared inside the loop, then the scope is only valid inside the loop and if used outside the loop will give an error.


2 Answers

This isn't a complete answer, partly because I'm not sure exactly what the question is (even though I found it quite interesting!).

Instead, I'll just present two alternative for-loops that do work. They've helped clarify the issues in my mind (in particular by helping me to understand for the first time why force() does anything at all in a call to lapply()). I hope they'll help you as well.

First, here is one that's a much closer equivalent of your properly function lapply() call, and which works for the same reason that it does:

a <- c(3,7,11)
f <- list()

## `for` loop equivalent of:
## f <- lapply(1:3, function(i) {force(i); function(x) a[i]+x})
for(i in 1:3) {
    f[[i]] <- {X <- function(i) {force(i); function(x) a[i]+x}; X(i)}
}
f[[1]](1)
# [1] 4

Second, here is one that does use local() but doesn't (strictly- or literally-speaking) rename i. It does, though, "rescope" it, by adding a copy of it to the local environment. In one sense, it's only trivially different from your functioning for-loop, but by focusing attention on i's scope, rather than its name, I think it helps shed light on the real issues underlying your question.

a <- c(3,7,11)
f <- list()

for(i in 1:3) {
   f[[i]] <- local({i<-i; function(x) a[i]+x})
}
f[[1]](1)
# [1] 4
like image 169
Josh O'Brien Avatar answered Oct 19 '22 19:10

Josh O'Brien


Will this approach work for you?

ff<-list()
for(i in 1:3) {
    fillit <- parse(text=paste0('a[',i,']+x' ))
   ff[[i]] <- function(x) ''
   body(ff[[i]])[1]<-fillit
}

It's sort of a lower-level way to construct a function, but it does work "as you want it to."

like image 28
Carl Witthoft Avatar answered Oct 19 '22 21:10

Carl Witthoft