Почему match.call полезен?

Например, в теле некоторых функций 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 имеет другое использование, которое я не обсуждаю, сохранить вызов в результирующем объекте.

1 ответ

Решение

Одна из причин, которая здесь уместна, заключается в том, что match.call фиксирует язык вызова, не оценивая его, и в этом случае позволяет lm рассматривать некоторые из "отсутствующих" переменных как "необязательные". Рассматривать:

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

В lm2, поскольку weights "отсутствует", но вы все еще используете его в weights=weightsR пытается использовать 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 не включает в себя пропущенные аргументы вообще.

Вы можете утверждать, что необязательные аргументы должны быть явно сделаны необязательными через значения по умолчанию, но здесь это не так.

Вот вам пример. В нем calc_1 - это функция с множеством числовых аргументов, которая хочет их сложить и умножить. Он делегирует эту работу функции calc_2, которая является вспомогательной функцией, которая принимает большинство этих аргументов. Но calc_2 также принимает некоторые дополнительные аргументы (от q до t), которые calc_1 не может предоставить из его собственных фактических параметров. Вместо этого он передает их как статистику.

Вызов calc_2 был бы поистине ужасным, если бы он был написан так, чтобы показать все, что ему передает calc_1. Поэтому вместо этого мы предполагаем, что если calc_1 и calc_2 имеют общий формальный параметр, они дают ему одно и то же имя. Это позволяет написать вызывающую программу, которая определяет, какие аргументы calc_1 может передать в calc_2, создает вызов, который будет это делать, и передает дополнительные значения для его завершения. Комментарии в приведенном ниже коде должны прояснить это.

Между прочим, библиотека tidyverse нужна только для%>% и str_c, с помощью которых я определил 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 
      )
Другие вопросы по тегам