6

Here's a simplified version of a function I have in a package that uses the ... argument and tidyselect to select variables:

# this toy function just selects the ... variables
foo <- function(dat = mtcars, ...){
  expr <- rlang::expr(c(...))
  cols_to_select <- tidyselect::eval_select(expr, data = dat)
  dplyr::select(dat, all_of(cols_to_select))
}

This works: foo(mtcars, cyl)

But my actual function has more preceding arguments before the ... argument, all with default values. It's tedious to type them all in cases where I call my function with those default values and pass a value to ... .

This is what I want - to assume dat = mtcars - but it does not work:

foo(... = cyl)

Error: Names must not be of the form ... or ..j.

Can I modify either the function or the call to allow direct specification of ... ?

Sam Firke
  • 21,571
  • 9
  • 87
  • 105
  • 1
    I'm struggling with a similar problem, and I ended up putting my supplemental arguments in a list, then use `do.call` to unpack them. See https://stackoverflow.com/a/3414202/3888000 for instance. You might be interested in trying `match.call(expand.dots = FALSE)$\`...\`` too. – Dan Chaltiel Mar 20 '20 at 16:37

1 Answers1

6

It is generally a good idea™ to put arguments with default values after the dots:

f <- function( ..., dat=mtcars )
    dplyr::select( dat, ... )

f(cyl)                 # Works
f(dat=iris, Species)   # Also works

If your API doesn't allow you to place named default arguments after the dots, here's another alternative. Note that you don't have to specify named arguments with default values explicitly. You can simply leave them "missing":

foo(, cyl)    # Same as foo( dat=mtcars, cyl )

If you have a lot of default arguments, and you don't want to keep putting a bunch of commas in your calls, you can make use of purrr::partial() to capture this pattern into a standalone function:

foo2 <- purrr::partial(foo, ,)  # Effectively partial(foo, dat=mtcars)
foo2(cyl)                       # Works

If that still involves typing more commas than you prefer, you can add one more step:

foo3 <- purrr::partial( foo, !!!purrr::keep(formals(foo), nzchar) )
foo3(cyl, mpg)                  # Also works
Artem Sokolov
  • 13,196
  • 4
  • 43
  • 74
  • 1
    Thanks! At this point I'm committed to `...` last for two reasons: inserting the `...` first would cause breakage of current code and the `...` functionality will be used infrequently compared to the other arguments. Upvoted but will leave the question open in case others have ideas for working with `...` in the final position. – Sam Firke Apr 02 '20 at 01:35
  • 2
    Thanks for the additions - the commas worked well for my purposes! – Sam Firke Apr 07 '20 at 02:36