为啥 match.call 有用?

Posted

技术标签:

【中文标题】为啥 match.call 有用?【英文标题】:Why is match.call useful?为什么 match.call 有用? 【发布时间】:2015-12-05 20:20:48 【问题描述】:

在一些 R 函数的主体中,例如 lm 我看到对 match.call 函数的调用。正如其帮助页面所说,当在函数match.call 中使用时,会返回一个指定参数名称的调用;这对于将大量参数传递给另一个函数应该很有用。

例如,在lm 函数中,我们看到对函数model.frame 的调用...

function (formula, data, subset, weights, na.action, method = "qr", 
model = TRUE, x = FALSE, y = FALSE, qr = TRUE, singular.ok = TRUE, 
contrasts = NULL, offset, ...) 

  cl <- match.call()
  mf <- match.call(expand.dots = FALSE)
  m <- match(c("formula", "data", "subset", "weights", "na.action", 
      "offset"), names(mf), 0L)
  mf <- mf[c(1L, m)]

  mf$drop.unused.levels <- TRUE
  mf[[1L]] <- quote(stats::model.frame)
  mf <- eval(mf, parent.frame())
  ...

...为什么这比我接下来直接调用 model.frame 指定参数名称更有用

function (formula, data, subset, weights, na.action, method = "qr", 
model = TRUE, x = FALSE, y = FALSE, qr = TRUE, singular.ok = TRUE, 
contrasts = NULL, offset, ...) 

  mf <- model.frame(formula = formula, data = data,
                    subset = subset, weights = weights, subset = subset)
  ...

(请注意,match.call 有另一个我不讨论的用途,将调用存储在结果对象中。)

【问题讨论】:

一方面,你没有把所有的都写出来。你可以做一些事情,比如一次传递所有参数,比如f &lt;- function(x, y, z) do.call("sum", as.list(match.call()[-1])) ; f(1, 2, 3) ## [1] 6。显然,这对于一长串命名参数会更有用 很公平,我想您也可以进行反向参数切片。 f &lt;- function(x, y, z)l &lt;- as.list(match.call())[-1];do.call(sum, l[setdiff(names(l), 'z')])。无论如何,我想我对lm 的使用感到困惑。但是我发现使用... 参数可以很容易地解决所有问题。我想我越来越挑剔了。 @Usobi:match.call() 在以下意义上是否更健壮?如果您使用...,那么您不知道传入了哪些参数,并且您最终可能会得到您不想要的东西,或者以意想不到的方式破坏事情。另一方面,如果您显式重复参数名称以传递它们,那么如果您更改函数参数定义,这将使代码更难重构。 @RichScriven 但要小心! f &lt;- function(x, y, z) a &lt;- 2; do.call("sum", as.list(match.call()[-1])); a &lt;- 1; f(a, 2, 3) 输出 7. 【参考方案1】:

与此相关的一个原因是match.call 捕获调用的语言而不对其进行评估,在这种情况下,它允许lm 将一些“缺失”变量视为“可选”。考虑:

lm(x ~ y, data.frame(x=1:10, y=runif(10)))

对比:

lm2 <- function (
  formula, data, subset, weights, na.action, method = "qr", 
  model = TRUE, x = FALSE, y = FALSE, qr = TRUE, singular.ok = TRUE, 
  contrasts = NULL, offset, ...
) 
  mf <- model.frame(
    formula = formula, data = data, subset = subset, weights = weights
  ) 

lm2(x ~ y, data.frame(x=1:10, y=runif(10)))
## Error in model.frame.default(formula = formula, data = data, subset = subset,  :
##   invalid type (closure) for variable '(weights)'

lm2 中,由于weights 是“缺失的”,但您仍在weights=weights 中使用它,R 尝试使用stats::weights 函数,这显然不是预期的。您可以通过在致电model.frame 之前测试缺失来解决此问题,但此时match.call 开始看起来不错。看看如果我们debug 调用会发生什么:

debug(lm2)
lm2(x ~ y, data.frame(x=1:10, y=runif(10)))
## debugging in: lm2(x ~ y, data.frame(x = 1:10, y = runif(10)))
## debug at #5: 
##     mf <- model.frame(formula = formula, data = data, subset = subset,
##         weights = weights)
## 
Browse[2]> match.call()
## lm2(formula = x ~ y, data = data.frame(x = 1:10, y = runif(10)))

match.call 根本不涉及缺少的参数。

您可能会争辩说,可选参数应该通过默认值显式设为可选,但这不是这里发生的情况。

【讨论】:

【参考方案2】:

这是一个例子。其中, calc_1 是一个带有大量数值参数的函数,需要将它们相加和相乘。它将这项工作委托给 calc_2 ,这是一个接受大部分这些参数的辅助函数。但是 calc_2 还需要一些额外的参数(q 到 t),而 calc_1 无法从它自己的实际参数中提供这些参数。相反,它将它们作为附加值传递。

对 calc_2 的调用如果写成显示 calc_1 通过它的所有内容,那将是非常可怕的。因此,我们假设如果 calc_1 和 calc_2 共享一个形参,它们将给它相同的名称。这使得编写一个调用程序成为可能,该调用程序计算出 calc_1 可以传递给 calc_2 的哪些参数,构造一个这样做的调用,并输入额外的值来完成它。下面代码中的 cmets 应该可以清楚地说明这一点。

顺便说一句,只有 %>% 和 str_c 需要库“tidyverse”,我用它定义了 calc_2,库“assertthat”用于一个断言。 (虽然在实际程序中,我会放入断言来检查参数。)

这是输出:

> calc_1( a=1, b=11, c=2, d=22, e=3, f=33, g=4, h=44, i=5, j=55, k=6
+       , l=66, m=7, n=77, o=8, p=88 
+       )
[1] "87654321QRST"

这是代码:

library( tidyverse )
library( rlang )
library( assertthat )


`%(%` <- call_with_extras
#
# This is the operator for calling
# a function with arguments passed
# from its parent, supplemented 
# with extras. See call_with_extras()
# below.


# A function with a very long
# argument list. It wants to call
# a related function which takes
# most of these arguments and
# so has a long argument list too.
# The second function takes some
# extra arguments.
#
calc_1 <- function( a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p )

  calc_2 %(% list( t = "T", q = "Q", s = "S", r = "R" )
  #
  # Call it with those extras, passing 
  # all the others that calc_2() needs
  # as well. %(% is my function for
  # doing so: see below.



# The function that we call above. It
# uses its own arguments q to t , as
# well as those from calc_1() .
#
calc_2 <- function( a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t )

  ( a + c * 10 + e * 100 + g * 1000 + i * 10000 + k * 100000 +
  m * 1000000 + o * 10000000 ) %>%
  str_c( q, r, s, t )
 


# Calls function f2 . Passes f2 whichever
# arguments it needs from its caller. 
# Corresponding formals should have the
# same name in both. Also passes f2 extra
# arguments from the named list extra. 
# The names should have the same names as
# corresponding formals of f2 .
#
call_with_extras <- function( f2, extras )
   
  f1_call <- match.call( sys.function(1), sys.call(1) )  
  # A call object.

  f1_actuals <- as.list( f1_call %>% tail(-1) ) 
  # Named list of f1's actuals.

  f1_formals <- names( f1_actuals )
  # Names of f1's formals.

  f2_formals <- names( formals( f2 ) )
  # Names of f2's formals.

  f2_formals_from_f1 <- intersect( f2_formals, f1_formals )
  # Names of f2's formals which f1 can supply.

  f2_formals_not_from_f1 <- setdiff( f2_formals, f1_formals )
  # Names of f2's formals which f1 can't supply.

  extra_formals <- names( extras ) 
  # Names of f2's formals supplied as extras.

  assert_that( setequal( extra_formals, f2_formals_not_from_f1 ) )
  # The last two should be equal.

  f2_actuals_from_f1 <- f1_actuals[ f2_formals_from_f1 ]
  # List of actuals which f1 can supply to f2.

  f2_actuals <- append( f2_actuals_from_f1, extras )
  # All f2's actuals.

  f2_call <- call2( f2, !!! f2_actuals )
  # Call to f2.

  eval( f2_call )
  # Run it.



# Test it.
#
calc_1( a=1, b=11, c=2, d=22, e=3, f=33, g=4, h=44, i=5, j=55, k=6
      , l=66, m=7, n=77, o=8, p=88 
      )

【讨论】:

以上是关于为啥 match.call 有用?的主要内容,如果未能解决你的问题,请参考以下文章

为啥没有用零填充

为啥装饰器有用? [关闭]

为啥电子邮件激活有用? [关闭]

闭包:为啥它们如此有用?

软堆:它在哪里以及为啥有用? [复制]

为啥 Numpy 掩码数组有用?