Problem

Function that take their main arguments via ... are great for interactive use, but sometimes a bit inconvenient when you want to do stuff in a batch manner.

Simple example

Interactive mode:

library(settings)
x <- options_manager(a = 1, b = 2)
x()
## $a
## [1] 1
## 
## $b
## [1] 2

Batch mode:

Suppose that values has been created by another upstream function and that you would like to feed that to options_manager.

values <- list(a = 1, b = 2)
x <- options_manager(values)
x()
## [[1]]
## [[1]]$a
## [1] 1
## 
## [[1]]$b
## [1] 2

Possibly not quite the value we would expect.

Solution

Simple draft

The key is to implement a function that handles values passed via ....

Let’s start out with a simple example:

foo <- function(...) {
  values <- list(...)
  nms <- names(values)
  if (is.null(nms)) {
    ## --> wrapped into list for batch setting
    do.call(options_manager, unlist(values, recursive = FALSE))
  } else if (any(idx <- nms == "")) {
    ## --> mixed
    values <- c(values[!idx], unlist(values[idx], recursive = FALSE))
    do.call(options_manager, values[sort(names(values))])
  } else {
    ## --> regular
    options_manager(...)
  }
}

That gives you the desired result no matter in what format you provide your input to settings::options_manager

opts <- foo(a = 1, b = 2)
opts()
## $a
## [1] 1
## 
## $b
## [1] 2
opts <- foo(list(a = 1, b = 2))
opts()
## $a
## [1] 1
## 
## $b
## [1] 2
opts <- foo(list(a = 1), b = 2)
opts()
## $a
## [1] 1
## 
## $b
## [1] 2

Generic proposal

This draft is a bit more generic.

  • handleThreedots takes care of transforming values that came in via ... so they are in the typical format that downstream functions taking their main input via ... would expect.

  • withHandledThreedots is simply a generic wrapper that combines calls to handleThreedots and the actual target function - which would be settings::options_manager in our example case.

handleThreedots <- function(...) {
  values <- list(...)
  nms <- names(values)
  if (is.null(nms)) {
    ## --> wrapped into list for batch setting
    unlist(values, recursive = FALSE)
  } else if (any(idx <- nms == "")) {
    ## --> mixed
    c(values[!idx], unlist(values[idx], recursive = FALSE))
    ## --> note that I did not introduce any sorting as before 
    ##     in order to not slow things down additionally 
  } else {
    ## --> regular
    values
  }
}
withHandledThreedots <- function(..., fun) {
  do.call(fun, handleThreedots(...))
}

Examples with only handleThreedots

handleThreedots(a = 1, b = 2, c = 3, d = 4)
## $a
## [1] 1
## 
## $b
## [1] 2
## 
## $c
## [1] 3
## 
## $d
## [1] 4
handleThreedots(list(a = 1, b = 2), list(c = 3, d = 4))
## $a
## [1] 1
## 
## $b
## [1] 2
## 
## $c
## [1] 3
## 
## $d
## [1] 4
handleThreedots(list(a = 1), b = 2, list(c = 3), d = 4)
## $b
## [1] 2
## 
## $d
## [1] 4
## 
## $a
## [1] 1
## 
## $c
## [1] 3

Actual example:

res <- withHandledThreedots(a = 1, b = 2, c = 3, d = 4, 
  fun = options_manager)
res()
## $a
## [1] 1
## 
## $b
## [1] 2
## 
## $c
## [1] 3
## 
## $d
## [1] 4
res <- withHandledThreedots(list(a = 1, b = 2), list(c = 3, d = 4),
  fun = options_manager)
res()
## $a
## [1] 1
## 
## $b
## [1] 2
## 
## $c
## [1] 3
## 
## $d
## [1] 4
res <- withHandledThreedots(list(a = 1), b = 2, list(c = 3), d = 4,
  fun = options_manager)
res()
## $b
## [1] 2
## 
## $d
## [1] 4
## 
## $a
## [1] 1
## 
## $c
## [1] 3

This approach can also handle additional arguments that fun might take:

res <- withHandledThreedots(a = 1, b = 2, c = 3, d = 4,
  fun = options_manager, .allowed = list(a = inrange(1, 2)))
res()
## $a
## [1] 1
## 
## $b
## [1] 2
## 
## $c
## [1] 3
## 
## $d
## [1] 4
try(res(a = 3))
res <- withHandledThreedots(list(a = 1, b = 2), list(c = 3, d = 4),
  fun = options_manager, .allowed = list(a = inrange(1, 2)))
res()
## $a
## [1] 1
## 
## $b
## [1] 2
## 
## $c
## [1] 3
## 
## $d
## [1] 4
try(res(a = 3))
res <- withHandledThreedots(list(a = 1), b = 2, list(c = 3), d = 4,
  fun = options_manager, .allowed = list(a = inrange(1, 2)))
res()
## $b
## [1] 2
## 
## $d
## [1] 4
## 
## $a
## [1] 1
## 
## $c
## [1] 3
try(res(a = 3))

Just to make sure that the additional argument .allowed of settings::options_manager was simply not shown because its name starts with a dot

foo <- function(..., special = FALSE) {
  if (special) {
    message("hello world!")
  }
  list(...)
}
withHandledThreedots(a = 1, b = 2, c = 3, d = 4, fun = foo)
## $a
## [1] 1
## 
## $b
## [1] 2
## 
## $c
## [1] 3
## 
## $d
## [1] 4
withHandledThreedots(a = 1, b = 2, c = 3, d = 4, fun = foo, special = TRUE)
## hello world!
## $a
## [1] 1
## 
## $b
## [1] 2
## 
## $c
## [1] 3
## 
## $d
## [1] 4