简体   繁体   English

为什么 match.call 有用?

[英]Why is match.call useful?

In the body of some R functions, for example lm I see calls to the match.call function.在一些 R 函数的主体中,例如lm我看到对match.call函数的调用。 As its help page says, when used inside a function match.call returns a call where argument names are specified ;正如它的帮助页面所说,在函数match.call返回一个指定参数名称的调用 and this is supposed to be useful for passing a large number of arguments to another functions.这对于将大量参数传递给另一个函数很有用。

For example, in the lm function we see a call to the function model.frame ...例如,在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())
  ...

... Why is this more useful than making a straight call to model.frame specifying the argument names as I do next? ...为什么这比直接调用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)
  ...

(Note that match.call has another use that I do not discuss, store the call in the resulting object.) (请注意, match.call还有一个我没有讨论的用途,将调用存储在结果对象中。)

One reason that is relevant here is that match.call captures the language of the call without evaluating it, and in this case it allows lm to treat some of the "missing" variables as "optional".与此处相关的一个原因是match.call捕获调用的语言而不对其进行评估,在这种情况下,它允许lm将某些“缺失”变量视为“可选”变量。 Consider:考虑:

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

Vs:对比:

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)'

In lm2 , since weights is "missing" but you still use it in weights=weights , R tries to use the stats::weights function which is clearly not what was intended.lm2 ,由于weights “缺失”但您仍然在weights=weights使用它,R 尝试使用stats::weights函数,这显然不是预期的。 You could get around this by testing for missingness before you call model.frame , but at that point the match.call starts looking pretty good.您可以通过在调用 model.frame 之前测试缺失来解决这个model.frame ,但此时match.call开始看起来非常好。 Look at what happens if we debug the 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 doesn't involve the missing arguments at all. match.call根本不涉及丢失的参数。

You could argue that the optional arguments should have been made explicitly optional via default values, but that's not what happened here.您可能会争辩说,应该通过默认值将可选参数明确设为可选,但这不是这里发生的情况。

Here's an example.这是一个例子。 In it, calc_1 is a function with loads of numerical arguments that wants to add and multiply them.其中,calc_1 是一个带有大量数值参数的函数,需要将它们相加和相乘。 It delegates this work to calc_2 , which is a subsidiary function that takes most of these arguments.它将这项工作委托给 calc_2 ,这是一个辅助函数,它接受大部分这些参数。 But calc_2 also takes some extra arguments (q to t) which calc_1 can't supply from its own actual parameters.但是 calc_2 还需要一些额外的参数(q 到 t),calc_1 不能从它自己的实际参数中提供这些参数。 Instead, it passes them as extras.相反,它将它们作为附加项传递。

The call to calc_2 would be truly horrendous if written so as to show everything calc_1 passes it.如果编写以显示 calc_1 通过它的所有内容,则对 calc_2 的调用将是非常可怕的。 So instead, we assume that if calc_1 and calc_2 share a formal parameter, they give it the same name.因此,我们假设如果 calc_1 和 calc_2 共享一个形式参数,则它们会为其赋予相同的名称。 This makes it possible to write a caller that works out which arguments calc_1 can pass to calc_2 , constructs a call that will do so, and feeds in the extra values to complete it.这使得可以编写一个调用程序来计算 calc_1 可以传递给 calc_2 的哪些参数,构造一个将这样做的调用,并输入额外的值来完成它。 The comments in the code below should make this clear.下面代码中的注释应该清楚地说明这一点。

Incidentally, library "tidyverse" is only needed for %>% and str_c which I defined calc_2 with, and library "assertthat" for one assertion.顺便说一句,库“tidyverse”只需要用于我定义 calc_2 的 %>% 和 str_c,以及用于一个断言的库“assertthat”。 (Though in a realistic program, I'd put in assertions to check the arguments.) (虽然在一个现实的程序中,我会放入断言来检查参数。)

Here's the output:这是输出:

> 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"

And here's the code:这是代码:

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 
      )

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM