使用 'bquote'(或替代方法)从符号构造函数

Constructing functions from symbols using 'bquote' (or alternatives to doing so)

假设我有一个“symbol”类型的对象,表示一个函数的名称。例如:

nm <- quote(mean)

我想构造一个函数 f,其函数体 使用 由符号 nm 命名的函数。例如:

f <- function(x, do = c("something", "nothing")) {
  switch(match.arg(do), something = mean(x), nothing = x)
}

我想完全相同构造这个函数,这意味着我不会满足以下方法:

factory <- function(name) {
  func <- match.fun(name)
  function(x, do = c("something", "nothing")) {
    switch(match.arg(do), something = func(x), nothing = x)
  }
}
g <- factory(nm)

因为 g 的正文不是 body(f) 并且 g 的环境不是 environment(f)

我考虑过的一种方法是 bquote:

h <- eval(bquote({
  function(x, do = c("something", "nothing")) {
    switch(match.arg(do), something = .(nm)(x), nothing = x)
  }
}))

bquote 让我了解了大部分内容,但一个问题是 hprint 输出不包含 nm 的替代值默认值:

h
## function(x, do = c("something", "nothing")) {
##     switch(match.arg(do), something = .(nm)(x), nothing = x)
##   }

print(h, useSource = FALSE)
## function (x, do = c("something", "nothing")) 
## {
##     switch(match.arg(do), something = mean(x), nothing = x)
## }

原因似乎是 hsrcref 属性:

identical(f, h)
## [1] TRUE
identical(f, h, ignore.srcref = FALSE)
## [1] FALSE

我的问题是: 如何解决从 nm 构建 f 的一般问题?

我对构造函数 h 的条件是 identical(f, h) 应该是 TRUE 并且 print(h) 的输出应该包含 nm 的替代值], 类似于 print(f).

我欢迎改进我现有 bquote 方法的答案,或建议新方法的答案,或解释为什么我想做的事情实际上不可能的答案...

不是特别优雅,但 parse(deparse( 似乎可行:

nm <- quote(mean)
f <- function(x, do = c("something", "nothing")) {
  switch(match.arg(do), something = mean(x), nothing = x)
}

eval(parse(text=deparse(bquote(h <- function(x, do = c("something", "nothing")) {
  switch(match.arg(do), something = .(nm)(x), nothing = x)
}))))

identical(f, h)
#> [1] TRUE
print(f)
#> function(x, do = c("something", "nothing")) {
#>   switch(match.arg(do), something = mean(x), nothing = x)
#> }
print(h)
#> function(x, do = c("something", "nothing")) {
#>     switch(match.arg(do), something = mean(x), nothing = x)
#> }

srcref 和预期的不一样:

identical(f, h, ignore.srcref = FALSE)
#> [1] FALSE
attributes(attributes(f)$srcref)$srcfile$lines
#> [1] "f <- function(x, do = c(\"something\", \"nothing\")) {"     
#> [2] "  switch(match.arg(do), something = mean(x), nothing = x)"
#> [3] "}"
attributes(attributes(h)$srcref)$srcfile$lines
#> [1] "h <- function(x, do = c(\"something\", \"nothing\")) {"     
#> [2] "    switch(match.arg(do), something = mean(x), nothing = x)"
#> [3] "}"

通读 ?srcref,似乎有两种惯用的方法可以改进 bquote 方法。第一个使用 removeSource 递归清理保留其源代码的函数:

h <- removeSource(eval(bquote({
  function(x, do = c("something", "nothing")) {
    switch(match.arg(do), something = .(nm)(x), nothing = x)
  }
})))
h
function (x, do = c("something", "nothing")) 
{
    switch(match.arg(do), something = mean(x), nothing = x)
}

第二种完全避免保留源代码:

op <- options(keep.source = FALSE)
h <- eval(bquote({
  function(x, do = c("something", "nothing")) {
    switch(match.arg(do), something = .(nm)(x), nothing = x)
  }
}))
options(op)
h
function (x, do = c("something", "nothing")) 
{
    switch(match.arg(do), something = mean(x), nothing = x)
}

实际上,?options 指出 keep.source 的默认值是 interactive(),因此这两种方法在非交互式上下文中都有些多余。