Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 884
directed laugh Avatar asked Nov 05 '22 02:11

directed laugh


1 Answers

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
like image 82
kohske Avatar answered Nov 07 '22 23:11

kohske