[R] Capturing Function Arguments

Iris Simmons |kw@|mmo @end|ng |rom gm@||@com
Sun Feb 18 11:59:03 CET 2024


Hi Reed,


I need to stress before giving my answer that no solution can handle
everything. These scenarios will always lead to problems:

* if any of the formal arguments rely on the current state of the call stack

* if any of the formal arguments rely on a variable that is only
defined later in the body of the function

That being said, this function does well to handle other scenarios:

```R
f <- function (...)
{
    ## replace f0 as needed
    wrapped_function <- f0
    call <- match.call(wrapped_function, expand.dots = FALSE)
    args <- as.list(call)[-1L]
    e <- new.env(parent = environment(wrapped_function))
    parent_frame <- parent.frame()
    formal_args <- formals(wrapped_function)
    for (sym in names(formal_args)) {
        ## if the formal argument is one of the provided arguments
        if (i <- match(sym, names(args), 0L)) {
            ## if the argument is missing, assign as is
            if (identical(args[[i]], quote(expr = )))
                e[[sym]] <- quote(expr = )
            ## if the argument is not ..., assign as a promise
            else if (sym != "...")
                eval(call("delayedAssign", quote(sym), args[[i]],
eval.env = quote(parent_frame), assign.env = quote(e)))
            else {
                ## handle ... separately
                ## create a variable corresponding to each dot argument,
                ## then capture them with get("...")
                dots <- args[[i]]
                for (i in seq_along(dots)) {
                    sym <- paste0("dd", i)
                    eval(call("delayedAssign", quote(sym), dots[[i]],
eval.env = quote(parent_frame)))
                    dots[[i]] <- as.symbol(sym)
                }
                e[["..."]] <- eval(as.call(c(function(...) get("..."), dots)))
            }
        }
        else {
            ## similar to above, but this time evaluate in e not parent_frame
            i <- match(sym, names(formal_args), 0L)
            if (identical(formal_args[[i]], quote(expr = )))
                e[[sym]] <- quote(expr = )
            else eval(call("delayedAssign", quote(sym),
formal_args[[i]], eval.env = quote(e), assign.env = quote(e)))
        }
    }
    ## you don't need to turn into a list, but you can
    args2 <- as.list(e, all.names = TRUE)
    list(call = call, args = args, args2 = args2, e = e)
    ## do whatever else you want to here
}
```

in the test scenario you described, it works:

```R
f0 <- function(x, y = 2 * z, z, a = NULL, b) NULL
a <- 1
x <- f(a, z = 1 + 100)
x$args2
```

produces:

```
> f0 <- function(x, y = 2 * z, z, a = NULL, b) NULL
> a <- 1
> x <- f(a, z = 1 + 100)
> x$args2
$x
[1] 1

$y
[1] 202

$z
[1] 101

$a
NULL

$b


>
```

Regards,
    Iris

On Sun, Feb 18, 2024 at 3:51 AM Reed A. Cartwright
<racartwright using gmail.com> wrote:
>
> I'm wrapping a function in R and I want to record all the arguments
> passed to it, including default values and missing values. I want to
> be able to snoop on function calls in sourced scripts as part of a
> unit testing framework.
>
> I can capture the values fine, but I'm having trouble evaluating them
> as if `force()` had been applied to each of them.
>
> Here is a minimal example:
>
> f0 <- function(x, y = 2 * z, z, a = NULL, b) NULL
>
> f <- function(...) {
>   call <- rlang::call_match(fn = f0, defaults = TRUE)
>   args <- rlang::call_args(call)
>   # do something here to evaluate args as if force() had been called
>   # I've tried many things but haven't found a solution that handled everything
>   args
> }
>
> # In the below example args1 and args2 should be the same
> a <- 1
> args1 <- f(a, z = 1 + 100)
>
> args2 <- list( a = 1, y = 202, z = 101, a = NULL, b = rlang::missing_arg() )
>
> If anyone knows how to get this to work, I would appreciate the help.
>
> Thanks,
> Reed
>
> --
> Reed A. Cartwright, PhD
> Associate Professor of Genomics, Evolution, and Bioinformatics
> School of Life Sciences and The Biodesign Institute
> Arizona State University
> ==================
> Address: The Biodesign Institute, PO Box 876401, Tempe, AZ 85287-6401 USA
> Packages: The Biodesign Institute, 1001 S. McAllister Ave, Tempe, AZ
> 85287-6401 USA
> Office: Biodesign B-220C, 1-480-965-9949
> Website: http://cartwrig.ht/
>
> ______________________________________________
> R-help using r-project.org mailing list -- To UNSUBSCRIBE and more, see
> https://stat.ethz.ch/mailman/listinfo/r-help
> PLEASE do read the posting guide http://www.R-project.org/posting-guide.html
> and provide commented, minimal, self-contained, reproducible code.



More information about the R-help mailing list