R - 作为函数列表的函数参数 - 检查参数而不评估?

R - function paramter that is list of functions--inspect parameter without evaluating?

编辑: 最初的反应表明我的文章将人们的注意力集中在最佳实践问题上,而不是技术问题上。然而,我想关注一个技术问题,下面只是一个玩具示例:

如果有人将列表传递给函数参数,您如何捕获和检查该列表的各个元素,而不冒系统尝试 call/evaluate 这些元素时出错的风险?

例如,如果用户将可能合适或不合适的功能列表传递给参数,或者加载了相关包,那么该功能如何安全地检查请求的功能?


假设我想构建一个循环访问可能应用的其他函数的函数。实际示例会调用不同的建模函数,但这里有一个更容易理解的玩具示例:

newfunc <- function(func.list){
  lapply(func.list, 
         function(f){
           f(letters)
         }
  )
}

假设newfunc() 可以采用的函数是nchar() 和length()。如果我们提供这些,我们会得到以下信息:

newfunc(
  func.list = list(nchar, length)
)


[[1]]
 [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

[[2]]
[1] 26

但是,假设 newfunc() 也可以使用 str_to_upper() 之类的东西,它来自包 stringr。传递 str_to_upper() 工作正常,但 only 如果 stringr 已预先加载:

newfunc(
  func.list = list(nchar, length, str_to_upper)
)

Error in lapply(func.list, function(f) f(letters)) : 
  object 'str_to_upper' not found


require(stringr)

newfunc(func.list = list(nchar, length, str_to_upper))
[[1]]
 [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

[[2]]
[1] 26

[[3]]
 [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O"
[16] "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z"


我想将代码放入可以调查列表元素并确定是否需要加载任何包(如 stringr)的函数中。另外,我想检查列出的函数是否来自可接受的集合(因此如果有人通过 mean() 或者更糟的是 rcorr() 来自未加载的 Hmisc).

# This works here but is undesireable:
newfunc(func.list = list(nchar, length, str_to_upper, mean))

# This creates issues no matter what:
newfunc(func.list = list(nchar, length, str_to_upper, rcorr))
require(Hmisc)
newfunc(func.list = list(nchar, length, str_to_upper, rcorr))

我知道如何执行 func.list.test <- deparse(substitute(func.list) 之类的操作来获取参数的文字文本,但我不知道如何在单个元素上执行此操作而不会在某些函数不存在时触发错误现在。

(而且我不想对 func.list.test 的整体解析输出采取字符串操作的 hacky 路线)

非常适合这个用例,我想知道这是否可以用基本的 R 技术来完成。但是,如果是 best/only 方式,请随意解释如何使用 tidy evaluation/quosures 等较新的方法来做到这一点(尽管我知道目前我对这些方法的熟悉程度非常有限)。

如有任何帮助,我们将不胜感激。

这是一个纯 base 函数,它使用 find() 确定正在使用的函数,并使用 help.search() 定位任何可能具有该函数的已安装软件包:

resolve <- function( func.list )
{
  ## Disassemble the supplied list of functions (lfs)
  lf <- as.list(substitute( func.list ))[-1]
  lfs <- lapply( lf, deparse )
  lfs <- setNames( lfs, lfs )

  ## Find functions (ff) in the loaded namespaces
  ff <- lapply( lfs, find )

  ## Existing functions (fex) are listed in the order of masking
  ##   The first element is used by R in the absence of explicit ::
  fex <- subset( ff, lapply(ff, length) > 0 )
  fex <- lapply( fex, `[`, 1 )

  ## Search for empty entries (ee) among installed packages
  ee <- names(subset( ff, lapply(ff, length) < 1 ))
  ee <- setNames( ee, ee )
  eeh <- lapply( ee, function(e)
      help.search( apropos = paste0("^", e, "$"),
                  fields = "name", ignore.case=FALSE )$matches$Package )

  ## Put everything together
  list( existing = fex, to_load = eeh )
}

用法示例:

resolve(func.list = list(nchar, length, str_to_upper, lag, between))
# List of 2
#  $ existing:List of 3
#   ..$ nchar : chr "package:base"
#   ..$ length: chr "package:base"
#   ..$ lag   : chr "package:stats"
#  $ to_load :List of 2
#   ..$ str_to_upper: chr "stringr"
#   ..$ between     : chr [1:3] "data.table" "dplyr" "rex"

library(dplyr)
resolve(func.list = list(nchar, length, str_to_upper, lag, between))
# List of 2
#  $ existing:List of 4
#   ..$ nchar  : chr "package:base"
#   ..$ length : chr "package:base"
#   ..$ lag    : chr "package:dplyr"
#   ..$ between: chr "package:dplyr"
#  $ to_load :List of 1
#   ..$ str_to_upper: chr "stringr"

library(data.table)
resolve(func.list = list(nchar, length, str_to_upper, lag, between))
# List of 2
#  $ existing:List of 4
#   ..$ nchar  : chr "package:base"
#   ..$ length : chr "package:base"
#   ..$ lag    : chr "package:dplyr"
#   ..$ between: chr "package:data.table"
#  $ to_load :List of 1
#   ..$ str_to_upper: chr "stringr"