缺失的点 (`...`) 在哪里处理?

Where do absent dots (`...`) get processed?

如果我们查看参数列表中包含点 ... 的函数的主体,我们通常可以找到接收这些点参数的函数。

例如,我们可以在 sapply() 的正文中看到点参数传递给 lapply()

sapply
# function (X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE) 
# {
#     FUN <- match.fun(FUN)
#     answer <- lapply(X = X, FUN = FUN, ...)
#     ## rest of function body
# }
# <bytecode: 0x000000000e05f0b0>
# environment: namespace:base>

然而,在lapply()中,参数列表中有点...,但函数体中没有

lapply
# function (X, FUN, ...) 
# {
#     FUN <- match.fun(FUN)
#     if (!is.vector(X) || is.object(X)) 
#         X <- as.list(X)
#     .Internal(lapply(X, FUN))
# }
# <bytecode: 0x0000000009414f08>
# <environment: namespace:base>

那么 lapply() 中的点 ... 参数在哪里得到处理? What/where他们被传给了吗?我们不能将它们传递给 match.fun()。我假设它们被传递到 .Internal() 但当我没有看到它们被传递到函数体中的任何函数时,我看不出有任何理由起作用。

它们没有明确地传递.Internal,但我相信它们可供do_lapply使用(在src/main/apply.c ) 通过动态作用域。作用域规则可能与通常的略有不同,因为 .Internal 是原始函数。

您可以看到 ... (R_DotsSymbol) 添加到 lapply 创建的函数调用中,因此它们可用于每个列表元素上的函数调用。 tmp 大致等同于 X[[i]]R_fcall 大致等同于 FUN(X[[i]], ...).

SEXP tmp = PROTECT(LCONS(R_Bracket2Symbol,
        LCONS(X, LCONS(isym, R_NilValue))));
SEXP R_fcall = PROTECT(LCONS(FUN,
             LCONS(tmp, LCONS(R_DotsSymbol, R_NilValue))));

However, in lapply(), there are dots ... in the argument list but not in the function body.

但是他们在lapply的body环境中!感兴趣的行是:

我们可以对 C++ 文件做一些非常相似的事情,我们可以在下面 Rcpp::sourceCpp 使用 Rf_lang3 而不是调用 ICONS:

 #include <Rcpp.h>

// [[Rcpp::export(rng = false)]]
SEXP to_simple_call_f_1(SEXP f, SEXP x, SEXP env){
  SEXP fn  = PROTECT(Rf_lang3(f, x, R_DotsSymbol)), 
       ans = Rf_eval(fn, env);
  UNPROTECT(1);
  return ans;
}

// [[Rcpp::export(rng = false)]]
SEXP to_simple_call_f_2(SEXP f, SEXP x, SEXP rho){
  SEXP fn  = PROTECT(Rf_lang3(f, x, R_DotsSymbol)), 
       ans = R_forceAndCall(fn, 1, rho);
  UNPROTECT(1);
  return ans;
}

// [[Rcpp::export(rng = false)]]
SEXP to_simple_call_f_3(SEXP f, SEXP x, SEXP rho){
  SEXP fn  = PROTECT(Rf_lang2(f, x)), 
       ans = Rf_eval(fn, rho);
  UNPROTECT(1);
  return ans;
}

我们现在可以看到:

f <- function(x, ...)
  x * unlist(list(...))
w_wont_work <- function(x, ...)
  to_simple_call_f_1(f = f, x = x, .GlobalEnv)

# first the solution that does not work (wrong env!)
a <- 3
b <- 2
w_wont_work(x, a, b)
#R> Error in to_simple_call_f_1(f = f, x = x, .GlobalEnv) : 
#R>   '...' used in an incorrect context

# now with the correct versions
w_1 <- function(x, ...)
  to_simple_call_f_1(f = f, x = x, environment())
w_2 <- function(x, ...)
  to_simple_call_f_2(f = f, x = x, environment())
# version that uses variables from the global environment instead
f3 <- function(x)
  x * unlist(list(a, b))
w_3 <- function(x)
  to_simple_call_f_3(f = f3, x = x, environment())

# check the functions
f  (2, a, b)
#R> [1] 6 4
w_1(2, a, b)
#R> [1] 6 4
w_2(2, a, b)
#R> [1] 6 4
w_3(2)
#R> [1] 6 4

# almost runs in the same time
bench::mark(f(2, a, b), w_1(2, a, b), w_2(2, a, b), w_3(2), min_time = 1, 
            max_iterations = 1e9)
#R> # A tibble: 4 x 13
#R>   expression        min  median `itr/sec` mem_alloc `gc/sec`  n_itr  n_gc total_time 
#R>   <bch:expr>   <bch:tm> <bch:t>     <dbl> <bch:byt>    <dbl>  <int> <dbl>   <bch:tm> 
#R> 1 f(2, a, b)      995ns  1.15µs   801375.        0B     25.1 733096    23      915ms 
#R> 2 w_1(2, a, b)   1.96µs  2.25µs   420623.    6.77KB     26.0 388953    24      925ms 
#R> 3 w_2(2, a, b)   1.93µs  2.21µs   424811.    6.77KB     25.9 393580    24      926ms 
#R> 4 w_3(2)         1.76µs  1.99µs   474501.   15.23KB     22.5 442139    21      932ms

I presume they are passed into .Internal() but I don't see any reason for this to work when I don't see them passed into any function in the function body.

注意 Rcpp 中的函数使用 .Call 而不是 .Internal:

to_simple_call_f_1
#R> function (f, x, env) 
#R> .Call(<pointer: 0x7fedc44cd1b0>, f, x, env)

于是,我通过了环境。所以我猜 .Internal 似乎通过了一个环境? ?.Internal 没有太大帮助。