Search code examples
rlapply

Specify ellipsis arguments as one object


I would like to loop through a function trying different combinations of parameters. I would like to specify the parameters in a list (i.e. a structured format)

In the following examples, I would like the outputs to be:

# n=0, m=0:    1,  2,  3,  4  
# n=1, m=0:    2,  3,  4,  5  
# n=1, m=10:  20, 30, 40, 50  

test <- function(x, n = 0, m = 1) {(x + n) * m}

vals <- list(1, 2, 3, 4)

arg_list <- list(args1 = list(),
                 args1 = list(n = 1), 
                 args2 = list(n = 1, m = 10))

lapply(vals, test, arg_list[[1]])
lapply(vals, test, arg_list[[2]])
lapply(vals, test, arg_list[[3]])

But this produces an error.

I can get the functionality I am seeking by doing the following, but this does not seem to be the most elegant implementation.

lapply(vals, FUN = function(x) do.call(test, c(x = x, arg_list[[1]])))
lapply(vals, FUN = function(x) do.call(test, c(x = x, arg_list[[2]])))
lapply(vals, FUN = function(x) do.call(test, c(x = x, arg_list[[3]])))

Essentially I am asking how to pass in ellipsis arguments as one object when used with lapply().

lapply(X, FUN, ...)

Any help greatly appreciated.


Solution

  • You just need one more layer:

    lapply(arg_list, \(arg)
      lapply(vals, \(val)
        do.call(test, c(x = val, arg))
      )
    )
    # $args1
    # $args1[[1]]
    # [1] 1
    # 
    # $args1[[2]]
    # [1] 2
    # 
    # $args1[[3]]
    # [1] 3
    # 
    # $args1[[4]]
    # [1] 4
    # 
    # 
    # $args1
    # $args1[[1]]
    # [1] 2
    # 
    # $args1[[2]]
    # [1] 3
    # 
    # $args1[[3]]
    # [1] 4
    # 
    # $args1[[4]]
    # [1] 5
    # 
    # 
    # $args2
    # $args2[[1]]
    # [1] 20
    # 
    # $args2[[2]]
    # [1] 30
    # 
    # $args2[[3]]
    # [1] 40
    # 
    # $args2[[4]]
    # [1] 50
    

    To demonstrate equivalency:

    ## from the question
    op = list(
      lapply(vals, FUN = function(x) do.call(test, c(x = x, arg_list[[1]]))),
      lapply(vals, FUN = function(x) do.call(test, c(x = x, arg_list[[2]]))),
      lapply(vals, FUN = function(x) do.call(test, c(x = x, arg_list[[3]])))
    )
    
    ## from the answer
    gregor = lapply(arg_list, \(arg)
      lapply(vals, \(x)
        do.call(test, c(x = x, arg))
      )
    )
    
    identical(op, unname(gregor))
    # [1] TRUE
    

    You could also do some automatic simplification by making the inner call an sapply instead of lapply:

    lapply(arg_list, \(arg)
      sapply(vals, 
        \(x) do.call(test, c(x = x, arg))
      )
    )
    $args1
    [1] 1 2 3 4
    
    $args1
    [1] 2 3 4 5
    
    $args2
    [1] 20 30 40 50
    

    (Note the repeated names in the output match the repeated names in the input.)