rnestedlexical-scopereferential-transparency

scope of nested R function


I have an example where I am not sure I understand scoping in R, nor I think it's doing the Right Thing. The example is modified from "An R and S-PLUS Companion to Applied Regression" by J. Fox

> make.power = function(p) function(x) x^p
> powers = lapply(1:3, make.power)
> lapply(powers, function(p) p(2))

What I expected in the list powers where three functions that compute the identity, square and cube functions respectively, but they all cube their argument. If I don't use an lapply, it works as expected.

> id = make.power(1)
> square = make.power(2)
> cube = make.power(3)
> id(2)
[1] 2
> square(2)
[1] 4
> cube(2)
[1] 8

Am I the only person to find this surprising or disturbing? Is there a deep satisfying reason why it is so? Thanks

PS: I have performed searches on Google and SO, but, probably due to the generality of the keywords related to this problem, I've come out empty handed.

PPS: The example is motivated by a real bug in the package quickcheck, not by pure curiosity. I have a workaround for the bug, thanks for your concern. This is about learning something.

After posting the question of course I get an idea for a different example that could clarify the issue.

> p = 1
> id = make.power(p)
> p = 2
> square = make.power(p)
> id(2)
[1] 4

p has the same role as the loop variable hidden in an lapply. p is passed by a method that in this case looks like reference to make.power. Make.power doesn't evaluate it, just keeps a pointer to it. Am I on the right track?


Solution

  • This fixes the problem

    make.power = function(p) {force(p); function(x) x^p}
    powers = lapply(1:3, make.power)
    lapply(powers, function(p) p(2))
    

    This issue is that function parameters are passed as "promises" that aren't evaluated until they are actually used. Here, because you never actually use p when calling make.power(), it remains in the newly created environment as a promise that points to the variable passed to the function. When you finally call powers(), that promise is finally evaluated and the most recent value of p will be from the last iteration of the lapply. Hence all your functions are cubic.

    The force() here forces the evaluation of the promise. This allows the newly created function each to have a different reference to a specific value of p.