R中的匹配函数:'match.fun()' vs 'devarse(substitute())' vs“直接”提供函数

xzabzqsa  于 2023-03-20  发布在  其他
关注(0)|答案(2)|浏览(137)

fun1()fun2()fun3()看起来都按预期工作:

fun1 <- function(fun, x) {
  fun(x)
}

fun1(mean, 1:10)
fun1(as.character, 1:10)
fun1(notafun, 1:10)

fun2 <- function(fun, x) {
  fun <- match.fun(fun)
  fun(x)
}

fun2(mean, 1:10)
fun2(as.character, 1:10)
fun2(notafun, 1:10)

fun3 <- function(fun, x) {
  fun <- deparse(substitute(fun))
  do.call(fun, list(x))
}

fun3(mean, 1:10)
fun3(as.character, 1:10)
fun3(notafun, 1:10)

到目前为止,我只注意到如果fun被指定为字符串(例如"mean"),match.fun()也可以工作。
我的用例是一个包中供本地使用的非导出函数(如果我不能将fun()指定为字符串,这不是一个限制)。使用match.fun()而不是“直接”提供函数(如fun1())有什么好处吗?

qni6mghb

qni6mghb1#

首先是文档!以下是?match.fun的相关部分:
当在以函数为参数的函数内部调用时,提取所需的函数对象,同时避免与其他类型的对象进行不必要的匹配。
如果FUN是一个函数,则返回它;如果它是一个符号(例如,用反引号括起来)或长度为1的字符向量,则在调用者的父环境中使用get查找它。
因此,match.fun具有两个主要优点:
1.它为用户提供了传递字符串和符号而不是函数的选项。
1.它提供了 * 类型安全 *,因为返回值总是一个函数。这不仅使你的源代码更健壮,而且更透明:不需要阅读fun2的文档就可以知道它的参数fun必须指定一个函数。
而且它几乎不以任何性能成本提供以下优势:

x1 <- mean
x2 <- "mean"
x3 <- quote(mean)
microbenchmark::microbenchmark(match.fun(x1), match.fun(x2), match.fun(x3), times = 1000L)
# Unit: nanoseconds
#           expr  min   lq     mean median   uq  max neval
#  match.fun(x1)  287  328  362.481    328  328 1681  1000
#  match.fun(x2) 1599 1681 1820.892   1681 1763 7544  1000
#  match.fun(x3) 1599 1640 1783.049   1681 1722 7339  1000

由于这些原因,在尝试计算函数调用之前,最好总是使用match.fun进行验证(就像在fun2中一样),而不是等待并希望调用能够得到评估(就像在fun1fun3中一样)。即使函数没有导出,即使从未传递字符串或符号,因为透明性(见2)使你的源代码更容易阅读和维护。
fun3的独特之处在于它允许用户传递未计算的表达式,但这种方法存在问题,原因有很多:
1.在其他函数内部无法按预期工作;请看@Hong Ooi的评论/回答。
1.不能传递使用双冒号或三冒号运算符访问的函数,也不能传递匿名函数,更一般地说,不能传递任何间接计算为函数的表达式:

fun3(base::mean, 1:10)
# Error in `base::mean`(1:10) : could not find function "base::mean"
fun3(function(x) mean(x), 1:10)
# Error in `function(x) mean(x)`(1:10) : 
#   could not find function "function(x) mean(x)"
fun3(match.fun(mean), 1:10)
# Error in `match.fun(mean)`(1:10) : 
#   could not find function "match.fun(mean)"

1.即使它 * 确实 * 如你所期望的那样工作,它也大多是烟雾和镜子:如果deparse(substitute(fun))的结果是一个字符串,命名了一个可以从调用环境访问的函数,那么首先就不需要deparse(substitute(fun)),因为fun无论如何都会计算出那个函数,它做的额外工作是没有意义的:

microbenchmark::microbenchmark(fun1(mean, 1:10), fun3(mean, 1:10), times = 1000L)
# Unit: microseconds
#              expr   min     lq      mean median     uq    max neval
#  fun1(mean, 1:10) 2.009  2.378  2.700055  2.460  2.788 14.350  1000
#  fun3(mean, 1:10) 9.020 10.127 10.991813 10.701 11.480 52.398  1000

总之,当你希望函数作为参数时,使用match.fun是一个很好的习惯。如果你想接受函数而不接受字符串或符号,你可以避免使用match.fun,但是在这种情况下,进行测试仍然是一个很好的习惯:

function(FUN, ...) {
  if (!is.function(FUN)) {
    stop("oops")
  }
  ## do stuff
}
ogq8wdun

ogq8wdun2#

一个关键区别是,如果在封闭函数中调用fun3,则fun3将 * 失败 *,例如:

g <- function(f, x)
{
    fun3(f, x)
}

g(mean, 1:10)
# Error in f(1:10) : could not find function "f"

一般来说,除非绝对必要,否则尽量避免非标准的求值技巧。

相关问题