Search code examples
rfunctionargumentsellipsis

pass a function a vector or undefined number of arguments


I want to be able to pass a function an undefined number of arguments via ... but also to be able to pass it a vector. Here is a silly example:

library(tidyverse)
df <- data.frame(gear = as.character(unique(mtcars$gear)),
                 id = 1:3)
myfun <- function(...) {
  ids_lst <- lst(...)
  df2 <- bind_rows(map(ids_lst, function(x) 
    mtcars %>% 
      filter(gear == x) %>% 
      select(mpg)), .id = "gear") %>% 
    left_join(df)
  df2
}
#these all work:
myfun(3)
myfun(3, 4)
myfun(3, 4, 5)

Passing it a vector doesn't work though:

myvector <- unique(mtcars$gear)
myfun(myvector)

The problem is because of the way the function collects the arguments and how it returns them:

myfun_lst <- function(...) {
  ids_lst <- lst(...)
  ids_lst
}
myfun_lst(3, 4, 5)
# $`3`
# [1] 3

# $`4`
# [1] 4

# $`5`
# [1] 5

myfun_lst(myvector)
# $myvector
# [1] 4 3 5

I thought a fix would be to test if the input is a vector, something like:

myfun_final <- function(...) {
  if(is.vector(...) & !is.list(...)) {
    ids_lst <- as.list(...)
    names(ids_lst) <- (...)
  } else { 
    ids_lst <- lst(...)
  }
  df2 <- bind_rows(map(ids_lst, function(x) 
    mtcars %>% 
      filter(gear == x) %>% 
      select(mpg)), .id = "gear") %>% 
    left_join(df)
  df2
}

Now, passing the function a vector works but collecting the arguments doesn't:

myfun_final(3, 4, 5)
myfun_final(myvector)

What is a good way to solve this? Thanks


Solution

  • How about testing if ... is of length 1 and if the only argument passed through is a vector? If not so, then consider ... a list of scalers and capture them with lst(...).

    myfun_final <- function(...) {
      if (...length() == 1L && is.vector(..1))
        ids_lst <- `names<-`(..1, ..1)
      else
        ids_lst <- lst(...)
      
      df2 <- bind_rows(map(ids_lst, function(x) 
        mtcars %>% 
          filter(gear == x) %>% 
          select(mpg)), .id = "gear") %>% 
        left_join(df)
      df2
    }
    

    Test

    > myfun_final(3)
    Joining, by = "gear"
       gear  mpg id
    1     3 21.4  2
    2     3 18.7  2
    3     3 18.1  2
    4     3 14.3  2
    5     3 16.4  2
    6     3 17.3  2
    7     3 15.2  2
    8     3 10.4  2
    9     3 10.4  2
    10    3 14.7  2
    11    3 21.5  2
    12    3 15.5  2
    13    3 15.2  2
    14    3 13.3  2
    15    3 19.2  2
    > myfun_final(3,4,5)
    Joining, by = "gear"
       gear  mpg id
    1     3 21.4  2
    2     3 18.7  2
    3     3 18.1  2
    4     3 14.3  2
    5     3 16.4  2
    6     3 17.3  2
    7     3 15.2  2
    8     3 10.4  2
    9     3 10.4  2
    10    3 14.7  2
    11    3 21.5  2
    12    3 15.5  2
    13    3 15.2  2
    14    3 13.3  2
    15    3 19.2  2
    16    4 21.0  1
    17    4 21.0  1
    18    4 22.8  1
    19    4 24.4  1
    20    4 22.8  1
    21    4 19.2  1
    22    4 17.8  1
    23    4 32.4  1
    24    4 30.4  1
    25    4 33.9  1
    26    4 27.3  1
    27    4 21.4  1
    28    5 26.0  3
    29    5 30.4  3
    30    5 15.8  3
    31    5 19.7  3
    32    5 15.0  3
    > myfun_final(c(3,4,5))
    Joining, by = "gear"
       gear  mpg id
    1     3 21.4  2
    2     3 18.7  2
    3     3 18.1  2
    4     3 14.3  2
    5     3 16.4  2
    6     3 17.3  2
    7     3 15.2  2
    8     3 10.4  2
    9     3 10.4  2
    10    3 14.7  2
    11    3 21.5  2
    12    3 15.5  2
    13    3 15.2  2
    14    3 13.3  2
    15    3 19.2  2
    16    4 21.0  1
    17    4 21.0  1
    18    4 22.8  1
    19    4 24.4  1
    20    4 22.8  1
    21    4 19.2  1
    22    4 17.8  1
    23    4 32.4  1
    24    4 30.4  1
    25    4 33.9  1
    26    4 27.3  1
    27    4 21.4  1
    28    5 26.0  3
    29    5 30.4  3
    30    5 15.8  3
    31    5 19.7  3
    32    5 15.0  3