LMc
LMc

Reputation: 18612

Tidyeval: How to pass a list with quoted elements to a nested function without user quoting?

Say I have two functions and I want to nest a within b:

library(dplyr)
library(rlang)

a <- function(var, values){
  filter(iris, {{var}} %in% values)
}

a(Species, "virginica") %>% head()

  Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
1          6.3         3.3          6.0         2.5 virginica
2          5.8         2.7          5.1         1.9 virginica
3          7.1         3.0          5.9         2.1 virginica
4          6.3         2.9          5.6         1.8 virginica
5          6.5         3.0          5.8         2.2 virginica
6          7.6         3.0          6.6         2.1 virginica

b calls a, but uses the dot-dot-dot ... for some other things. It has a list argument that is passed to a:

b <- function(l, ...){
  eval_tidy(expr(a(!!! l)))
}

b(l = list(var = quo(Species), values = "virginica")) %>% head() # works if user passes quosure

Can I pass a list of arguments to b to be evaluated in a when some of the elements need to be quoted? So the call to b would look like the following:

b(l = list(var = Species, values = "virginica")) # does not work

I have tried the following, but it seems I need to split-splice one more time or something:

b <- function(l, ...){
  l <- enquos(l)
  qq_show(a(!!! l))
}

b(l = list(var = Species, values = "virginica"))

# a(^list(var = Species, values = "virginica"))

Other attempts result in premature evaluation of Species and therefore the error: object 'Species' not found.

Upvotes: 3

Views: 165

Answers (1)

Lionel Henry
Lionel Henry

Reputation: 6803

Create a helper function that collects arguments for a(). This helper can defuse var with enquo() so the user does not have to use quo().

a <- function(var, values) {
  filter(iris, {{ var }} %in% values)
}

a_opts <- function(var, values) {
  list(var = enquo(var), values = values)
}

Then b() expects the user to pass a list of arguments presumably created with a_opts(). But how do we pass these arguments to a()? We can't use !!! directly because splicing is only enabled in ... (see dynamic dots) and a() doesn't take ....

One solution is to use do.call(). Another solution idiomatic to rlang is to enable !!! explicitly with inject():

b <- function(l) {
  inject(a(!!!l))
}

Example usage:

a_opts(Species, "virginica") %>%
  b() %>%
  head()

Upvotes: 1

Related Questions