[R] Capturing Function Arguments

Reed A. Cartwright r@c@rtwr|ght @end|ng |rom gm@||@com
Mon Feb 19 02:55:28 CET 2024


Thanks Ivan and Iris for your solutions, I'll look over them.

The solution that I came up with last night involves creating a
function that has the same formals signature as the wrapped function
and relying on `environment()` and `list(...)` to return a function's
variables at the beginning of the call. Since some default variables
can't be successfully created at the beginning of the function, I
manually clean up the formals as needed so that
`as.list(environment())` won't fail. I'm thinking about returning a
copy of the environment instead of a list to avoid having to clean up
the formals, as I can selectively ignore problematic arguments later
instead of modifying the signature ahead of time.

Thanks again for the suggestions,
Reed

```
capture <- function(fun, env = parent.frame()) {
    fun_name <- deparse(substitute(fun))
    # extract formal arguments and append ... if needed
    fmls <- formals(fun)
    if(! "..." %in% names(fmls)) {
        fmls <- c(fmls, alist("..." = ))
    }

    # construct wrapped function
    f <- function() TRUE
    formals(f) <- fmls
    body(f) <- bquote({
        args <- as.list(environment())
        args <- c(args, list(...))
        mc <- match.call()
        mc[[1L]] <- quote(.(substitute(fun)))
        res <- eval.parent(mc)
        out <- list(fun = .(fun_name),
            args = args,
            result = res
        )
        invisible(structure(out, class="function_call"))
    })
    environment(f) <- env
    return(f)
}

# example usage
hist <- capture(graphics::hist.default)
formals(hist) <- c(formals(hist), list(xname = NA_character_))
```

On Sun, Feb 18, 2024 at 4:17 AM Ivan Krylov <ikrylov using disroot.org> wrote:
>
> В 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