How to test if a function argument is a "quosurable" name?

174 Views Asked by At

I'm making a function that should be able to handle multiple classes for its first argument: formulas, characters, tidy-selection, var names... The goal is then to use tidyselection with tidyselect::vars_select, except with bare formulas.

The problem is that when I test the class of this argument, it will throw an error if the value is a name to be tidy-selected, since it will be considered as a not found object.

I found a workaround with tryCatch, which enquotes the first argument if its evaluation fails (and thus if it doesn't exist in this scope).

library(rlang)
foo=function(.vars){
    .vars2=tryCatch(.vars, error=function(e) enquo(.vars))
    print(class(.vars2))
    print(class(.vars))
}

foo(Species) 
# [1] "quosure" "formula"
# Error in print(class(.vars)) : object 'Species' not found
# In addition: Warning message:
# In print(class(.vars)) : restarting interrupted promise evaluation

foo(~Species)
# [1] "formula"
# [1] "formula"

foo(1) 
# [1] "numeric"
# [1] "numeric"

foo("Species")
# [1] "character"
# [1] "character"

This doesn't seem clean to me, as I'm catching all errors without filtering on my specific case.

Is there a built-in function to test this, or a cleaner solution than this workaround?

2

There are 2 best solutions below

2
On

I think the following is what you are trying to do (using here only base R).

foo=function(.vars) {
  .vars2 = substitute(.vars)
  ifelse(is.symbol(.vars2), class(.vars2), class(.vars))
  }

foo(Species) 
#[1] "name"
foo(~Species)
#[1] "formula"
foo(1)
#[1] "numeric"
foo("Species")
#[1] "character"
0
On

I don't think that there is a function which lets you avoid a structured control flow along the different input types.

library(rlang)
library(tidyselect)
library(dplyr)

foo <- function(df, .vars){
  en_vars <- enquo(.vars)
  var_expr <- quo_get_expr(en_vars)
  
  if (is.name(var_expr)){
    vars_select(names(df), !! en_vars)
  } else if (is_formula(var_expr)) {
    vars_select(names(df), all.vars(.vars))
  } else {
    vars_select(names(df), .vars)
  }
}

iris_tbl <- as_tibble(iris)

foo(iris_tbl, Species) 
#>   Species 
#> "Species"

foo(iris_tbl, ~Species)
#>   Species 
#> "Species"

foo(iris_tbl, 1) 
#> Note: Using an external vector in selections is ambiguous.
#> ℹ Use `all_of(.vars)` instead of `.vars` to silence this message.
#> ℹ See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
#> This message is displayed once per session.
#>   Sepal.Length 
#> "Sepal.Length"

foo(iris_tbl, "Species")
#>   Species 
#> "Species"

Created on 2020-06-21 by the reprex package (v0.3.0)