nicolas
nicolas

Reputation: 9805

Promises in lapply / R

I am not sure what the promises are doing in R

If one runs

a = lapply(seq_len(2), function(n) { function() {n}})
b = lapply(seq_len(2), function(n)  {n})

we can see that

a[[1]]() # == 2
b[[1]]   # == 1

I understand that R uses promise's object and lazily evaluates an expression in its environment, but I dont understand why the different environments created for each function would not contain their own value for n.

[[1]]
function () 
{
    n
}
<environment: 0x7f9b2416ad18>

[[2]]
function () 
{
    n
}
<environment: 0x7f9b2416ab20>

as.list(environment(a[[1]])) 
$n
[1] 2

as.list(environment(a[[2]]))
$n
[1] 2

Is it be possible to fix the semantic through the lapply function somehow ?

lapply
function (X, FUN, ...) 
{
    FUN <- match.fun(FUN)
    if (!is.vector(X) || is.object(X)) 
        X <- as.list(X)
    .Internal(lapply(X, FUN))
}
<bytecode: 0x7f9b25150f18>
<environment: namespace:base>

PS : refocused question

Edit : concretely, is it possible to write a lapply2 function that generically "forces" the argument to have uniform behaviour as in :

pl <- lapply (1:3, function(y) { force(y); function(x) pow(x,y) } )
pl <- lapply2(1:3, function(y) { function(x) pow(x,y) } )

Upvotes: 5

Views: 389

Answers (2)

davidski
davidski

Reputation: 581

I posted a comment a while ago that this might be the case as of recent versions of R but here is an offical proof as well that lapply now behaves exactly like your lapply2 version, taken from the news release of R 3.2.0.

  • Higher order functions such as the apply functions and Reduce() now force arguments to the functions they apply in order to eliminate undesirable interactions between lazy evaluation and variable capture in closures. This resolves PR#16093.

Upvotes: 1

Alexander Hanysz
Alexander Hanysz

Reputation: 801

I find it easier to understand in this form:

f=function(n) {function() {n}}
x=1
a=f(x)
x=2
a()
[1] 2

The key part of the documentation is

When a function is called the arguments are matched and then each of the formal arguments is bound to a promise. The expression that was given for that formal argument and a pointer to the environment the function was called from are stored in the promise.

After the call a=f(x), the function argument n is bound to a promise with the name x and a pointer to the global environment .GlobalEnv.

In your lapply examples, the anonymous function function(n) { function() {n}} is called from the global environment each time. This is why every element of the list a gets the same value of n: it's coming from the global environment. I don't see how it's possible to change this behaviour by rewriting lapply.

Upvotes: 2

Related Questions