3

I want to write an R function arg2str that returns the names (that is a vector of strings) of the symbols that are fed as arguments.

For the most simple case, I only have one input symbol:

library ("rlang")

arg2str.v0 <- function (arg) rlang::quo_name (enquo (arg))
arg2str.v0 (a)
## [1] "a"

If I have multiple symbols, I can use the three-dots construct:

arg2str.v1 <- function (...) sapply (enquos (...), rlang::quo_name)
arg2str.v1 (a, b, c)
##             
## "a" "b" "c"

(Subsidiary question: why is the resulting vector of strings displayed with a preliminary line break instead of a preliminary [1] in this case?)

But I actually want to deal with vectors of symbols. Yet:

sym2str.v1 (c(a, b, c))
##
## "c(a, b, c)"

How do I tune my function to do so?


My first intuition was to first enquote the symbols contained in the argument vector by using sapply (instead of enquoting the argument vector itself), then to apply rlang::quo_name to the resulting vector of quosures. But it seems that the symbols in the argument vector are evaluated within sapply before enquote is called on each of them:

arg2str.v2 <- function (args) {
    enquo_args <- sapply (args, enquo)
    lapply (enquo_args, rlang::quo_name)
}
arg2str.v2 (c (a, b, c))
## Error in lapply(X = X, FUN = FUN, ...) : object 'a' not found
Robin LP
  • 41
  • 4

3 Answers3

3

Using rlang conventions, this should work:

return_args <- function(args){

    args_expr <- enexpr(args)

    if(length(args_expr) == 1) {
        args_vars <- as.list(args_expr)
    } else {
        args_vars <- as.list(args_expr)[-1]
    }

    sapply(args_vars, quo_name)
}


return_args(c(a, b, c))
[1] "a" "b" "c"

return_args(a)
[1] "a"

Valeri Voev
  • 1,982
  • 9
  • 25
1

I am not sure if there is a vectorised tidyeval operation for what you want. You can try

f <- function(v) {
    v <- rlang::quo_name(enquo(v))
    gsub('^c\\(|\\s|\\)$', '', v) %>% 
        strsplit(',') %>% 
        unlist 
}

f(c(a, b, c))
#[1] "a" "b" "c"

which will work with inputs of the form a, c(a) or c(a, b) but it's a bit hacky...

The answer to your side question (why arg2str.v1(a, b, c) does not print the "[1]" at the beginning of the line) is that it returns a named vector, whose names are empty strings (compare e.g. with the output of set_names(c('a', 'b', 'c'), c('', '', ''))).

konvas
  • 14,126
  • 2
  • 40
  • 46
  • Hacky indeed, but it does work! Yet, it does not allow named vectors as inputs, e.g. `f(c(n1=a, n2=b))` (additional feature that I did not specify in my question). I guess one could come up with a regular expression to get and preserve such names. – Robin LP Aug 31 '18 at 16:47
  • And thanks also for your answer to the side question. It makes a lot of sense! – Robin LP Aug 31 '18 at 16:53
1

One possible hacky answer using substitute and deparse:

arg2str.v3 <- function (args) {
    strs <- sapply (substitute (args), deparse)
    if (length (strs) > 1) { strs <- strs [-1] }
    return (strs)
}
arg2str.v3 (c (a, b, c))
## [1] "a" "b" "c"

Note that it also preserves names if a named vector is provided as input:

arg2str.v3 (c (n1 = a, n2 = b, n3 = c))
##  n1  n2  n3 
## "a" "b" "c"
Robin LP
  • 41
  • 4