Why are variable values in closures getting lost after repeatedly calling lapply?

Go To StackoverFlow.com

5

I'm attempting to use a series of lapply calls to build a list of curried functions, which ideally at the last lapply call, returns the final desired value. The currying works, but lapply seems to always applies the last element in the list after the second application.

Example:

curry <- function(fn, ...) {
  arglist <- list(...)
  function(...) {
    do.call(fn, append(arglist, list(...)))
  }
}
# rcurry is used only to init the first lapply.
rcurry <- function(v1, fn, ...) {
  arglist <- append(list(v1), list(...))
  function(...) {
    do.call(fn, append(arglist, list(...)))
  }
}

myadd <- function(a,b,c) {
  a+b+c
}

This works as expected:

# you can achieve the same by closure:
# curry.a <- lapply(c(10, 1000), FUN = function(a) { curry(myadd, a) })
curry.a <- lapply(list(10, 1000), rcurry, myadd)
curry.a[[1]](1,2)
curry.a[[2]](1,2)

# > [1] 13
# > [1] 1003

The next lapply of curry "mangles the scope":

# this does give the desired output:
# curry.a.b <- list(curry(curry.a[[1]], 1), curry(curry.a[[2]], 1))
curry.a.b <- lapply(curry.a, curry, 1)
curry.a.b[[1]](2)
curry.a.b[[2]](2)

# > [1] 1003
# > [1] 1003

It doesn't seem like a result of the curry or rcurry function. Using roxygen's Curry function does the same thing. creating curry.a by closure above or using curry.a <- list(curry(myadd, 10), curry(myadd, 1000)) also results the same.

And of course the final curry:

# it doesn't work if you re-define this:
# curry.a.b <- list(curry(curry.a[[1]], 1), curry(curry.a[[2]], 2))
curry.a.b.c <- lapply(curry.a.b, curry, 2)
lapply(curry.a.b.c, do.call, list())

# > [1] 1003
# > [1] 1003

What's going on here?

2012-04-04 21:25
by directed laugh
lapply evaluates FUN in a new environment. I think that has something to do with it - Joshua Ulrich 2012-04-04 22:39
I think the answer is the same as in Tommy's reply to http://stackoverflow.com/questions/9950144/access-lapply-index-names-inside-fun/9950734#comment12707459_995073 - 42- 2012-04-05 03:04
Joshua is warm, kohske nails it. Thank you all - directed laugh 2012-04-05 05:51


2

fn in curry is not evaluated in the scope of function and hence it is promise. If you force it then you can get what you expect:

curry <- function(fn, ...) {
  force(fn)
  arglist <- list(...)
  function(...) {
    do.call(fn, append(arglist, list(...)))
  }
}

then,

> curry.a.b <- lapply(curry.a, curry, 1)
> curry.a.b[[1]](2)
[1] 13
> curry.a.b[[2]](2)
[1] 1003
> 
> curry.a.b.c <- lapply(curry.a.b, curry, 2)
> lapply(curry.a.b.c, do.call, list())
[[1]]
[1] 13

[[2]]
[1] 1003

More internally, lapply generates a local variable X that is referred by each call of function. If X is not evaluated in each function when calling the lapply, X is promise. After calling lapply, X in all function call from lapply returns same (i.e., last) value. So lapply is similar with:

f0 <- function(i) function() i
f1 <- function(i) {force(i); function() i}

f <- local({
 r0 <- list()
 r1 <- list()
 for (i in 1:2) {
    r0[[i]] <- f0(i)
    r1[[i]] <- f1(i)
 }
 list(r0 = r0, r1 = r1)
})

then,

> f$r0[[1]]()
[1] 2
> f$r1[[1]]()
[1] 1
> f$r0[[2]]()
[1] 2
> f$r1[[2]]()
[1] 2
2012-04-05 04:48
by kohske
100% Bulls eye. Thank you! I actually thought the closure is the promise, so I did closure <- function(...) { do.call(fn, append(arglist, list(...)) }; force(closure); return closure. Of course, that didn't work - directed laugh 2012-04-05 05:49
Ads