[R] Capturing Function Arguments

Ivan Krylov |kry|ov @end|ng |rom d|@root@org
Sun Feb 18 12:16:57 CET 2024


В Sat, 17 Feb 2024 11:15:43 -0700
"Reed A. Cartwright" <racartwright using gmail.com> пишет:

> I'm wrapping a function in R and I want to record all the arguments
> passed to it, including default values and missing values.

This is hard if not impossible to implement for the general case
because the default arguments are evaluated in the environment of the
function as it is running:

f0 <- function(arg = frobnicate()) {
 frobnicate <- switch(
  sample.int(3, 1),
  function() environment(),
  function(n=1) runif(n),
  function() alist(a=)$a
 )
 arg
}

(And some arguments aren't meant to be evaluated at all.)

Even starting with rlang::call_match(call = NULL, defaults = TRUE) is
doomed to a certain extent because it gives you f(x = a, a = NULL) for
both function(x, a = NULL), f(a) (where `a` passed as an argument `x`
and `a` should be taken from the parent frame) and function(x = a, a =
NULL), f() (in which case `x` defaults to `a`, which in turn defaults
to NULL).

I think the key here is evaluating the arguments first, then matching.
This makes a lot of assumptions about the function being inspected: no
NSE, no ellipsis, formals don't depend on the body, nothing weird about
the environment of the function...

f <- function(...) {
 .makemissing <- function() alist(a=)$a
 .ismissing <- function(x) identical(x, .makemissing())

 # prepare to evaluate formals
 params <- formals(f0)
 e <- new.env(parent = environment(f0))
 # assign non-missing formals
 for (n in names(params)) if (!.ismissing(params[[n]])) eval(
  # work around delayedAssign quoting its second argument
  call('delayedAssign', n, params[[n]], e, e)
 )

 # match the evaluated arguments against the names of the formals
 args <- as.list(match.call(f0, as.call(c('f0', list(...)))))[-1]
 for (n in names(args)) assign(n, args[[n]], envir = e)

 # evaluate everything, default argument or not
 mget(names(params), e, ifnotfound = list(.makemissing()))
}

f0 <- function(x, y = 2 * z, z, a = NULL, b) NULL
a <- 1
identical(
 f(a, z = 1 + 100),
 list(x = 1, y = 202, z = 101, a = NULL, b = rlang::missing_arg())
)
# [1] TRUE

-- 
Best regards,
Ivan



More information about the R-help mailing list