I'm defining functions in a loop and trying to force evaluation of a loop variable without having to carry around a private environment.
Example: a set of functions handlers$h1
, handlers$h2
, ..., handlers$h6
that just pass through the 1, 2, ..., 6 to another function like so:
handlers <- list()
for (i in 1:6) {
handlers[[paste0('h', i)]] <- function () {
message(i) # <-- example
}
}
So handlers$h1()
should message 1, handlers$h2()
should message 2, ...
Instead, all of the functions return 6
, the current value of i
.
To get around this I can use a closure as specified in this question
msg <- function(i) {
force(i)
function () { message(i) }
}
for (i in 1:6) {
handlers[[paste0('h', i)]] <- msg(i)
}
Now each function works as expected, but each function has to carry around its own environment:
handlers$h1
# function () { message(i) }
# <environment: 0x9342b80>
How can I make it so that handlers$h1
prints function () { message(1) }
, i.e. evaluates the i
and substitutes it directly into the definition, removing the need for the environment?
The only ways I can think of are to:
- use
eval
(something I prefer not to do);
- write out each definition by hand with the 1-6 substituted directly (fine in this case where there are only 6 functions, but in general not scalable)
Here are some approaches that use body<-
You could use bquote
handlers <- list()
for (i in 1:6) {
handlers[[paste0('h', i)]] <- function () {}
body( handlers[[paste0('h', i)]]) <- bquote(message(.(i)))
}
handlers$h1
## function ()
## message(1L)
or substitute
for (i in 1:6) {
handlers[[paste0('h', i)]] <- function () {}
body( handlers[[paste0('h', i)]]) <- substitute(message(i), list(i=i))
}
Unfortunately base R lacks a function for making functions by hand, but pryr supplies make_function
:
library(pryr)
handlers <- list()
for (i in 1:6) {
body <- substitute(message(i), list(i = i))
f <- make_function(alist(), body)
handlers[[paste0('h', i)]] <- f
}
Note the use of substitute
to manually modify a quoted call.
Another cool (IMO!) function in pryr is unenclose
, which unencloses a function by substituting in the variables defined in the enclosing environment:
msg <- function(i) {
force(i)
function () message(i)
}
msg(1)
# function () message(i)
# <environment: 0x102dc6ca0>
unenclose(msg(1))
# function ()
# message(1)
But their really is no downside to using the original closure.
Here are two ways. They are the same except for the ## line in each:
formals<-
handlers <- list()
f <- function() message(i)
for (i in 1:6) {
formals(f) <- list(i = i) ##
handlers[[paste0('h', i)]] <- f
}
trace
handlers <- list()
f <- function() message(i)
for (i in 1:6) {
trace(f, bquote(i <- .(i)), print = FALSE) ##
handlers[[paste0('h', i)]] <- f
}