3

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.

LMc
  • 12,577
  • 3
  • 31
  • 43
  • 1
    You need to take the list of expressions by value (no `enquo()`) and require your users to use _external quoting_. See https://tidyeval.tidyverse.org/dplyr.html#sec:external-quoting – Lionel Henry Oct 19 '21 at 20:29
  • Ah reading again, you had already figured out that the user had to defuse the expressions themself (in this case by using `quo()`). I see no other reasonable way. Are you sure you need to structure the interface in this way? It seems weird to me to collect heterogeneous arguments in a list like that. – Lionel Henry Oct 20 '21 at 06:35
  • @LionelHenry Generally, I have a quoting function nested in a quoting function. I'm not sure how to pass a subset of arguments from the parent to the nested function when the `...` is unavailable for use. The other issue is that, like many functions, the user might specify some subset of arguments for the nested function, not necessarily all of them -- which is why I was trying to split-slice a list. – LMc Oct 20 '21 at 17:33
  • ok I _think_ I understand the problem, I have added an anwser. – Lionel Henry Oct 22 '21 at 07:26

1 Answers1

1

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()
Lionel Henry
  • 6,652
  • 27
  • 33