0

Hadley's "Advanced R" book contains the following example that I have a hard time understanding.

add <- function(x) {
  function(y) x + y
}
adders <- lapply(1:10, add)
adders[[1]](10)
#> [1] 20
adders[[10]](10)
#> [1] 20

x is lazily evaluated the first time that you call one of the adder functions. At this point, the loop is complete and the final value of x is 10. Therefore all of the adder functions will add 10 on to their input, probably not what you wanted!

I check and it is indeed true that x within each adders[[num]] evaluates to 10.

get("x", envir=environment(adders[[2]])) # = 10
get("x", envir=environment(adders[[9]])) # = 10

But why? For example, when adders[[2]] is defined as the returned value of add(2), at that moment x = 2. According to Hadley, "a function preserves the environment in which it was defined." So why didn't adders[[2]] preserve the environment in which x = 2?

Heisenberg
  • 8,386
  • 12
  • 53
  • 102
  • 3
    does this help http://stackoverflow.com/questions/16129902/explain-a-lazy-evaluation-quirk – rawr Aug 12 '14 at 22:24
  • @rawr Thanks for the link. There's still something I don't understand (I added a comment on the original post). But I'll ask here as well. When I call the first adder function, the lapply loop is already over. Why does the value of `x = 10` persists? Where exactly does the adder function look to find x? – Heisenberg Aug 12 '14 at 22:41
  • there is an x in each of the environments created in the loop as you showed with `get`, they were created but not evaluated. all the exes are evaluated **first** when you evaluate one of the 10 adders. run the function with `force` in the linked question to see the difference – rawr Aug 12 '14 at 23:16
  • @rawr So when the `x`'s are evaluated for the first time, where do they look to get their values? All the explanations say that they look at the last value of the `lapply` loop. However, when the first adder is called the `lapply` loop is already over, so there must be a link somehow between the `adder`'s environment and the `lapply` loop. I hope to ask what that link is. – Heisenberg Aug 13 '14 at 03:13
  • 1
    @Anh `lapply` creates it's own environment. It's basically running a `for` loop for you. It's kind of like `for(x in 1:10) add(x)`. All the adders then reference that same `x` indexing variable. If you set `add <- function(x) {print(parent.frame());function(y) x + y}` and then run the `lapply` you can see they all have a common calling parent frame. – MrFlick Aug 13 '14 at 04:22
  • @MrFlick That's very helpful. I tried `add <- function(x) { print(parent.frame()) print(environment()) function(y) x + y }`, which shows that they share parent frame (i.e., the `lapply` environment), but have different `environment()` of their own. Is it correct to think that by adding `force(x)`, we force the evaluation of `x` in adders' own environments, which is where they will look first even before the `parent.frame`? – Heisenberg Aug 13 '14 at 15:44
  • @MrFlick Also, why do the `adders` search for `x` in the `parent.frame` but not in the `parent.environment`? Is that how functions are supposed to work? – Heisenberg Aug 13 '14 at 15:45
  • 1
    @Anh So this is a bit tricky because the parent frame and the parent environment aren't the same thing. The arguments you pass to a function are evaluated in the calling environment (parent frame). But other variables are evaluated in the parent environment. So each of the adders receive a promise for the value of `x` from the calling frame in their own environment, but without `force()`, this promise isn't evaluated. Functions don't search for variables in the calling environment (parent frame) by default. It's tricky because in R code you can't "see" a promise without evaluating it. – MrFlick Aug 13 '14 at 15:53

0 Answers0