R scope: force variable substitution in function w

2020-02-29 04:03发布

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)

标签: r scope
3条回答
SAY GOODBYE
2楼-- · 2020-02-29 04:36

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.

查看更多
Summer. ? 凉城
3楼-- · 2020-02-29 04:41

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))
}
查看更多
Fickle 薄情
4楼-- · 2020-02-29 04:52

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 
}
查看更多
登录 后发表回答