简体   繁体   中英

R & quosures - How to get names of symbols contained in a vector passed as function argument?

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

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"

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 eg with the output of set_names(c('a', 'b', 'c'), c('', '', '')) ).

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"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM