I'd like to create a function that checks if naked expressions are contained in a vector or not. That function should work independently of the case of the input data.
A simple solution to evaluate a variety of expressions is to use tidyselect::eval_select()
. E.g. here's a dummy function that checks whether or not expressions are a
, b
or c
.
is_abc <- function(...) {
expr <- rlang::expr(c(...))
data <- rlang::set_names(letters)
out <- tidyselect::eval_select(expr, data)
names(out) %in% letters[1:3]
}
is_abc(a, b, z)
#> [1] TRUE TRUE FALSE
is_abc(c(a, b, z))
#> [1] TRUE TRUE FALSE
Created on 2023-12-27 by the reprex package (v2.0.1)
The problem is that I don't think it's possible to make it case-insensitive without editing tidyselect::eval_select()
(or using a special selection helper).
is_abc(c(a, B, z))
#> Error in `is_abc()`:
#> ! Can't subset columns that don't exist.
#> ✖ Column `B` doesn't exist.
Alternatively, I could use rlang::enexprs()
to get a list of symbols that will then be converted to character type when changing the case or comparing to the vector:
is_abc <- function(...) {
exprs <- rlang::enexprs(...)
tolower(exprs) %in% tolower(letters[1:3])
}
is_abc(a, b, z)
#> [1] TRUE TRUE FALSE
is_abc(a, B, z)
#> [1] TRUE TRUE FALSE
Created on 2023-12-27 by the reprex package (v2.0.1)
The problem now is that this won't work for more complex expressions since that will convert entire expressions to strings:
is_abc(c(a, b, z))
#> [1] FALSE
Is there a simple solution to this problem so that I can have a function working in both cases, without depending on the case of the input?
For example:
# desired behaviour
is_abc(c(a, B), z)
#> [1] TRUE TRUE FALSE
You could do the whole thing in base R:
is_abc <- function(...) {
args <- substitute(...())
recurse <- FALSE
if(".recurse" %in% names(args)) {
args <- args[-match(".recurse", names(args))]
recurse <- TRUE
}
out <- unlist(lapply(args, function(x) {
if(is.call(x)) {
x <- as.list(x)
if(!identical(x[[1]], quote(c))) {
stop("is_abc does not know what to do with calls to function '",
as.character(x[[1]]), "'")
}
unlist(do.call("is_abc", c(x[-1], .recurse = 1)))
} else as.character(x)
}))
if(recurse) toupper(out) else toupper(out) %in% c("A", "B", "C")
}
Testing:
is_abc(a, b, z)
#> [1] TRUE TRUE FALSE
is_abc(A, b, z)
#> [1] TRUE TRUE FALSE
is_abc(c(a, b, z))
#> [1] TRUE TRUE FALSE
is_abc(c(a, B, z))
#> [1] TRUE TRUE FALSE
is_abc(c(a, B), z)
#> [1] TRUE TRUE FALSE
is_abc(a, b, c(z, c, c(a, b, c(z, y, c(a, b, c)))))
#> [1] TRUE TRUE FALSE TRUE TRUE TRUE FALSE FALSE TRUE TRUE TRUE