Split up `…` arguments and distribute to multiple functions

后端 未结 3 1327
温柔的废话
温柔的废话 2020-11-27 05:22

Using the following function foo() as a simple example, I\'d like to distribute the values given in ... two different functions, if possible.

相关标签:
3条回答
  • 2020-11-27 05:42

    Separate Lists If you really want to pass different sets of parameters to different functions then it's probably cleaner to specify separate lists:

    foo <- function(x, y, sum = list(), grep = list()) {
     list(sum = do.call("sum", c(x, sum)), grep = do.call("grep", c("abc", y, grep)))
    }
    
    # test
    
    X <- c(1:5, NA, 6:10)
    Y <- "xyzabcxyz"
    foo(X, Y, sum = list(na.rm = TRUE), grep = list(value = TRUE))
    
    ## $sum
    ## [1] 55
    ## 
    ## $grep
    ## [1] "xyzabcxyz"
    

    Hybrid list / ... An alternative is that we could use ... for one of these and then specify the other as a list, particularly in the case that one of them is frequently used and the other is infrequently used. The frequently used one would be passed via ... and the infrequently used via a list. e.g.

    foo <- function(x, y, sum = list(), ...) {
     list(sum = do.call("sum", c(x, sum)), grep = grep("abc", y, ...))
    }
    
    foo(X, Y, sum = list(na.rm = TRUE), value = TRUE)
    

    Here are a couple of examples of the hybrid approach from R itself:

    i) The mapply function takes that approach using both ... and a MoreArgs list:

    > args(mapply)
    function (FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE, USE.NAMES = TRUE) 
    NULL
    

    ii) nls also takes this approach using both ... and the control list:

    > args(nls)
    function (formula, data = parent.frame(), start, control = nls.control(), 
        algorithm = c("default", "plinear", "port"), trace = FALSE, 
        subset, weights, na.action, model = FALSE, lower = -Inf, 
        upper = Inf, ...) 
    NULL
    
    0 讨论(0)
  • 2020-11-27 05:45

    You can only pass the ... argument to another function, if that other function includes all named arguments that you pass to ... or if it has a ... argument itself. So for sum, this is no problem (args(sum) returns function (..., na.rm = FALSE)). On the other hand grep has neither na.rm nor ... as an argument.

    args(grep)
    # function (pattern, x, ignore.case = FALSE, perl = FALSE, value = FALSE, 
    #     fixed = FALSE, useBytes = FALSE, invert = FALSE) 
    

    This does not include ... and also does not include a named argument na.rm either. A simple solution is to just define your own function mygrep as follows:

    mygrep <- function (pattern, x, ignore.case = FALSE, perl = FALSE, value = FALSE, 
                        fixed = FALSE, useBytes = FALSE, invert = FALSE, ...)
      grep(pattern, x, ignore.case, perl, value, fixed, useBytes, invert)
    

    Then it seems to work:

    foo <- function(x, y, ...){
      list(sum = sum(x, ...), grep = mygrep("abc", y, ...))
    }
    X <- c(1:5, NA, 6:10)
    Y <- "xyzabcxyz"
    foo(X, Y, na.rm = TRUE, value = TRUE)
    
    # $sum
    # [1] 56
    # 
    # $grep
    # [1] "xyzabcxyz"
    
    0 讨论(0)
  • 2020-11-27 06:03
    1. Why does grep error before sum?

      See that sum is a lot more accommodating with its arguments:

      X <- c(1:5, NA, 6:10)
      sum(X, na.rm = TRUE, value = TRUE)
      ## [1] 56
      

      It doesn't failed because it doesn't care about other named arguments, so the value = TRUE simplifies to just TRUE which sums to 1. Incidentally:

      sum(X, na.rm = TRUE)
      ## [1] 55
      
    2. How to split ... to different functions?

      One method (that is very prone to error) is to look for the args for the target functions. For instance:

      foo <- function(x, y, ...){
          argnames <- names(list(...))
          sumargs <- intersect(argnames, names(as.list(args(sum))))
          grepargs <- intersect(argnames, names(as.list(args(grep))))
          list(sum = do.call(sum, c(list(x), list(...)[sumargs])),
               grep = do.call(grep, c(list("abc", y), list(...)[grepargs])))
      }
      

      This is prone to error anytime the arguments a function uses are not properly reported by args, such as S3 objects. As an example:

      names(as.list(args(plot)))
      ## [1] "x"   "y"   "..." ""   
      names(as.list(args(plot.default)))
      ##  [1] "x"           "y"           "type"        "xlim"        "ylim"       
      ##  [6] "log"         "main"        "sub"         "xlab"        "ylab"       
      ## [11] "ann"         "axes"        "frame.plot"  "panel.first" "panel.last" 
      ## [16] "asp"         "..."         ""           
      

      In this case, you could substitute the appropriate S3 function. Because of this, I don't have a generalized solution for this (though I don't know that it does or does not exist).

    0 讨论(0)
提交回复
热议问题