Tidiest way to quote bare names in dots (with rlang?)

rlang

#1

I have a function that takes (...) as arguments. I want the user to be able to supply the (...) as bare names. The return value of the function should be a named character vector. So:

fun <- function(...) { }
fun(blah = blubb, foo = bar)
#
# expected result: list(blah = "blubb", foo = "bar")

I already found a solution to my problem that works, but it is not super elegant:

fun <- function(...) {
  dots <- eval(substitute(alist(...)))
  purrr::map_chr(dots, function(x){
    if (inherits(x, "name")){
      deparse(x)
    } else {
      as.character(x)
    }
  })
}

fun(blah = blubb, thisalso = "works")

I was wondering if there is a nicer solution with rlang


#2

You can use rlang::exprs(...) as a replacement for eval(subsitute(alist(...))); and I’m not sure why you need the logic inside the if statement (I’m probably missing something).

Either way here’s a (marginally) updated version of your working function that produces the same result:

fun_tidy <- function(...) {
  dots <- exprs(...)
  purrr::map_chr(dots, as.character)
}

E.g.:

original <- fun(blah = blubb, thisalso = "works")
tidy <- fun_tidy(blah = blubb, thisalso = "works")
identical(original, tidy)
#> TRUE

And both should work if the named item is also passed in as a character:

fun("blah" = "blubb", thisalso = "works")
fun_tidy("blah" = "blubb", thisalso = "works")

#3

Would the same ~pattern as the one from @Edwin’s post (below) make sense?

multiple characters to names: syms

bare_to_quo_mult_chars <- function(x, ...) {
grouping <- rlang::syms(...)
 x %>% group_by(!!!grouping) %>% summarise(nr = n())
}
bare_to_quo_mult_chars(mtcars, list("vs", "cyl"))

#4

Hmm the documentation of rlang::exprs() even says that it is equivalent to eval(substitute(alist(...))). I am tempted to stay with eval(substitute(alist(...))), since both eval and substitute are internals/primitive, while rlang::exprs() is a convoluted rabbithole of functions if you try to follow the source code down.

Thanks for the tip with as.character() though, I would not have expected that it works that straight forward with names.


#5

I agree with you re: the rabbit hole of functions that you can end up with relying on external packages vs. base functionality sometimes; I suppose in this instance it might be the case that the rabbit hole will get shallower over time as the tidy evaluation framework matures.

And I think the simplification for the as.character() call is just because a named list can be defined with characters or symbols/names for the list names; e.g.

> identical(list("name" = "Jim"), list(name = "Jim"))
#> TRUE

(But that thought is only based on the fact that both work, not because of some deeper understanding for the moment!)


#6

Hi, compat_lazy_dots() may help with this, I saw that @hadley used it in dbplyr here: https://github.com/tidyverse/dbplyr/blob/1d840ad930a1bc35be9780a1d0018164c195560f/R/tbl-lazy.R#L77-L80 . He calls the functions from dplyr, butI noticed that it also works with rlang:

test <- function(.data, ..., .dots = list()) {
  dots <- rlang:::compat_lazy_dots(.dots, rlang:::caller_env(), ...)
  
  dots
}

test(hello = "one", world = "two")

Returns:

$hello
<quosure: global>
~one

$world
<quosure: global>
~two



#7

Neat! It’s written up in the for package authors section of the dplyr compatibility vignette as well- part of the compatibility file, below!


#8

hmm thanks for all the input @mara @edgararuiz . I guess for those solutions I would have to make my whole code use tidyeval (which I don’t really have the time for right now). For now i’ll stick with the named character vector solution, but i’ll keep compat_lazy_dots in mind for later.


Best way to do tidy eval on a package with *some* optional inputs?