Search code examples
rfunctiongetter-settervariadic

How to get all parameters passed into a function, with their values?


I have a function

adebo.deepSearch = function(z, pi_0 = 0.3, families=list(), ... )
    {

    }

I want to capture all of the parameter names and values passed in by way of a function called grabFunctionParameters; e.g.,

adebo.deepSearch = function(z, pi_0 = 0.3, families=list(), ... )
    {
    args = grabFunctionParameters();
    }

Where args would be a list with "keys" and "values", such as

args[["pi_0"] = 0.3;

For all keys and values, including those in the ellipses (...).

An ideal (variadic) solution would be an external function grabFunctionParameters()

Solution:

Here is the provided ACCEPTED ANSWER:
# https://stackoverflow.com/questions/66329835/
# nice work :: B. Christian Kamgang
# .GlobalEnv$.function.args.memory ... key memory on last function call ... so I could reference outside the function
grabFunctionParameters <- function() {
    pf <- parent.frame()    
    args_names <- ls(envir = pf, all.names = TRUE, sorted = FALSE)
    if("..." %in% args_names) {
    dots <- eval(quote(list(...)), envir = pf)
    }  else {
    dots = list()
    }
    args_names <- sapply(setdiff(args_names, "..."), as.name)
    if(length(args_names)) {
    not_dots <- lapply(args_names, eval, envir = pf) 
    } else {
    not_dots <- list()
    }   
   idx <- names(dots) != "";
   list(.keys. = names(not_dots), .vals. = unname(not_dots), .fn. = as.character(sys.call(1L)[[1L]]), .scope. = pf, .dot.keys. = names(dots[idx]), .dot.vals. = unname(dots[idx])); 
} 

Here is the provided ACCEPTED ANSWER (formatted a bit differently):
grabFunctionParameters <- function() 
    {
    pf          = parent.frame();    
    my.names    = ls(envir = pf, all.names = TRUE, sorted = FALSE);
    
    dots        = if("..." %in% my.names) { eval(quote(list(...)), envir = pf); } else { list(); }  
    dots.idx    = ( names(dots) != "" );
    
    remaining   = sapply( setdiff(my.names, "..."), as.name);
    
    not.dots    = if(length(remaining) > 0) { lapply( remaining, eval, envir = pf);  } else { list(); }
    
   
    res = list();
    
        res$.fn.            = as.character( sys.call(1L)[[1L]] );
        res$.scope.         = pf;
        res$.keys.          = names( not.dots );
        res$.vals.          = not.dots;                             # unname(not_dots);  # I want keys on "vals"
        res$.dots.keys.     = names( dots[dots.idx] );
        res$.dots.vals.     = dots[dots.idx];                       # unname(dots[dots.idx]); 

    res;
    } 

Solution

  • Here is one possible solution. This solution requires function arguments with no default values to be specified (like z below).

    grabFunctionParameters <- function() {
      pf <- parent.frame()                                   # get caller environment
      dots <- eval(quote(list(...)), envir = pf)             # get ... in the caller
      nms <- sapply(ls(envir = pf, sorted = FALSE), as.name) # get argument names different from names in ... in the caller
      out <- c(lapply(nms, eval, envir = pf), dots)          # get all arguments/values
      out[names(out) != ""]                                  # remove unnamed values in ... (if any)
    }
    

    Example of use case

    adebo.deepSearch = function(z, pi_0 = 0.3, families=list(), ... ) {
      args = grabFunctionParameters();
      args
    }
    

    Some scenarios

    adebo.deepSearch(z=4)
    # $z
    # [1] 4
    # 
    # $pi_0
    # [1] 0.3
    # 
    # $families
    # list()
    # 
    adebo.deepSearch(z=4, pi_0=9, families = list(z=1:2))  
    # $z
    # [1] 4
    # 
    # $pi_0
    # [1] 9
    # 
    # $families
    # $families$z
    # [1] 1 2
    # 
    # 
    adebo.deepSearch(z=4, pi_0=9, ac=5, bc=6)  # some additional arguments for ...
    # $z
    # [1] 4
    # 
    # $pi_0
    # [1] 9
    # 
    # $families
    # list()
    # 
    # $ac
    # [1] 5
    # 
    # $bc
    # [1] 6
    

    Udapte: this is an update of the function above to make it more general.

    it always returns a list:

    • an empty list if the caller (function) has no argument (or only ... with unnamed values).
    • formal argument names (not in ...) could start with dot. The previous function required the caller to have ...; and the caller with formal argument names starting with dot (not in ...) were not return.

    New function

    grabFunctionParameters <- function() {
        pf <- parent.frame()    
        args_names <- ls(envir = pf, all.names = TRUE, sorted = FALSE)
        if("..." %in% args_names) {
        dots <- eval(quote(list(...)), envir = pf)
        }  else {
        dots = list()
        }
        args_names <- sapply(setdiff(args_names, "..."), as.name)
        if(length(args_names)) {
        not_dots <- lapply(args_names, eval, envir = pf) 
        } else {
        not_dots <- list()
        }
        out <- c(not_dots, dots)
        out[names(out) != ""]                                  # remove unnamed values in ... (if any)
    }   
    

    Some scenarios

    fn1 <- function() grabFunctionParameters()                              # the initial function (before the update) required ... argument
    fn2 <- function(x=1, .a=2, b=list(), ...) grabFunctionParameters()      # the initial function did not return .a 
    fn3 <- function(.x, .a=2, b=list(), ...) grabFunctionParameters()
    fn4 <- function(...) grabFunctionParameters()
    fn5 <- function(x, .a) grabFunctionParameters()                        # the initial function required ... argument
    
    
    fn1()     # correct since the caller has no argument. Previously not allowed!
    # list()
    
    fn2()
    # $x
    # [1] 1
    # 
    # $.a
    # [1] 2
    # 
    # $b
    # list()
                                        
    fn2(.a=10, ac=4, bc=7, .xy=1)      #    
    # $x
    # [1] 1
    # 
    # $.a
    # [1] 10
    # 
    # $b
    # list()
    # 
    # $ac
    # [1] 4
    # 
    # $bc
    # [1] 7
    # 
    # $.xy
    # [1] 1
    
    fn3(10)
    # $.x
    # [1] 10
    # 
    # $.a
    # [1] 2
    # 
    # $b
    # list()
    
    fn3()       # throw an error! (.x required!). This will not happen if we use mget function and not lapply/supply inside grabFunctionParameters above. 
    # Error in FUN(X[[i]], ...) : argument ".x" is missing, with no default
    
    fn4(a = 5, b = 6, c = 6, 6, 7, 9)       # unnamed values are dropped
    # $a
    # [1] 5
    # 
    # $b
    # [1] 6
    # 
    # $c
    # [1] 6
    
    fn5(6, 8)
    # $x
    # [1] 6
    # 
    # $.a
    # [1] 8