rrlang

Capture ellipsis / three dots within a function; ignoring explicitly mentioned arguments


I would like to collect the names of additional arguments on a function (ellipsis / three dots).

The following example works as expected

CollectDots <- function(...) {
  as.character(rlang::ensyms(...))
}
CollectDots(A, B, C)
# "A" "B" "C"

However, if the function contains one or more parameters (either with/without default value), the output is not as expected.

CollectDots1 <- function(Var1 = NA, ...) {
  as.character(rlang::ensyms(...))
}
CollectDots1(A, B, C) # 1 explicit parameter, returning only B and C
# "B" "C"

CollectDots2 <- function(Var1 = NA, Var2, ...) {
  as.character(rlang::ensyms(...))
}
CollectDots2(A, B, C) # 2 explicit parameters, returning C
# "C"

Further, if var1 = 10, why the following function does not print 10 and throws an error

CollectDots3 <- function(Var1 = 10, Var2, ...) {
  print(Var1)
  as.character(rlang::ensyms(...))
}
CollectDots3(A, B, C)
# Error: object 'A' not found

How to only collect the names of additional arguments provided to the function (...), excluding parameters explicitly mentioned in the function?


Solution

  • When you define a function, for example: function(Var1 = NA, ...), you're saying that the function takes one named argument Var1 and if nothing is present for that argument (either named or in that position), use NA. In the CollectDots1() function, you get only "B" and "C" because it assumes you want to specify Var1=A. If you put the dots first, before the named arguments, you get what you expect.

    CollectDots1 <- function(..., Var1 = NA) {
      as.character(rlang::ensyms(...))
    }
    
    CollectDots1(A, B, C)
    #> [1] "A" "B" "C"
    

    In CollectDots3() below, the function assumes your specifying Var1=A and Var2=B. Since you're then trying to print A without A being defined, the error is generated.

    CollectDots3 <- function(Var1 = 10, Var2, ...) {
      print(Var1)
      as.character(rlang::ensyms(...))
    }
    CollectDots3(A, B, C)
    

    Again, putting the three dots first will give you what you expect:

    CollectDots3 <- function(..., Var1 = 10, Var2) {
      print(Var1)
      as.character(rlang::ensyms(...))
    }
    CollectDots3(A, B, C)
    #> [1] 10
    #> [1] "A" "B" "C"
    

    Note, the above only works because you're not calling Var2 which has no default and is not defined in the call to the function. If your function tried to access Var2 without it being named, then you would get an error:

    CollectDots3 <- function(..., Var1 = 10, Var2) {
      print(Var1)
      cat(Var2)
      as.character(rlang::ensyms(...))
    }
    CollectDots3(A, B, C)
    #> [1] 10
    #> Error in CollectDots3(A, B, C): argument "Var2" is missing, with no default
    

    Finally, there is one implication of doing things this way (i.e., putting the three dots first) -- it is that you must always explicitly name all of the named arguments to the function when you call it. For example, if you wanted to use Var1=20, you would have to say CollectDots3(Var1=20)