RRR
RRR

Reputation: 133

Clarification on list subsetting within a function

Please see the following code. I try to add functions which are not same to func_list . However, func_list[[1]] and func_list[[2]] show same value. I want the result that func_list[[1]](1) = 2, but it fails. Please tell me where I should correct my code.

func_list = list()

for (k in 1:2) {
  z= k
  func_list[[k]] = function(y, zz = z) {y + zz}
}

a1 = 2
a1 = 3
e1 = func_list[[1]](1)
e2 = func_list[[2]](1)

Upvotes: 1

Views: 69

Answers (1)

r2evans
r2evans

Reputation: 160437

This is a common mistake. The reference to z (from zz = z) is not made at assignment: it is done "lazily" when needed. This means that R does not try to resolve z until you call it in the assignment to e1. It will first check for previous arguments named z (and default to that if found). Then it will check in the calling namespace/environment, which in this case is the global environment. Since the z is defined within the for loop and persists in the global environment, then it is found and used. Of course, as PabloRod suggested, the value stored in z is the one that was last assigned to it: the value 2.

Side note: if you rm(z), you'll see:

rm(z)
e1 = func_list[[1]](1)
# Error in func_list[[1]](1) : object 'z' not found

Indicating that it had previously found it in the current environment.

One thing to realize is that functions are not just "formals" (arguments) and body, but a function is technically a "closure" that includes with it the enclosing environment. You can see this when you run

environment(func_list[[1]])
# <environment: R_GlobalEnv>

meaning that outside of the namespace between the two curly braces that enclose the function's body, the next namespace searched for things is this one, R_GlobalEnv. Okay, let's try something else:

func_list = list()

for (k in 1:2) {
  z= k
  func_list[[k]] = local({ z = k; function(y, zz = z) {y + zz}; })
}

e1 = func_list[[1]](1)
e2 = func_list[[2]](1)
e1
# [1] 2
e2
# [1] 3
environment(func_list[[1]])
# <environment: 0x000000001b51adf8>

First, note that your e1 and e2 are (I believe) what you expect. Second, the environment immediately surrounding your func_list[[1]] is no longer the global environment. In this case, it happens to be a temporary environment instantiated by local (which "creates a new, empty environment"). In this environment, we define z = k and then define the function, and this environment-with-a-function is returned, stored within func_list.

Another way to do this that bypasses the need for local (and, by the way, is strictly required if you ever get into dynamically creating shiny reactive objects) is to do this instead:

func_list2 <- lapply(1:2, function(k) function(y, zz = k) {y + zz})
e1 = func_list2[[1]](1)
e2 = func_list2[[2]](1)
e1
# [1] 2
e2
# [1] 3
environment(func_list2[[1]])
# <environment: 0x000000001b536280>
environment(func_list2[[2]])
# <environment: 0x000000001b536088>

since each time the anonymous function (function(k) ...) is run, it is given its own temporary environment.

After all this, it might be useful to read a better-organized discussions of namespaces, environments, and their search order, such as http://adv-r.had.co.nz/Environments.html.

Upvotes: 4

Related Questions