Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accept both bare (from rlang) or string as a function input

Tags:

r

rlang

quosure

I editing an existing function in a package. Currently, the function accepts a column name in a data frame as a string. I am updating the function to accept either a string name or a bare name. But I am running into some issues.

The general approach I'd like to take is to convert a bare to a string, so the rest of the function does not need to be updated. If the user passes a string column name, then I don't need to modify the input.

The code below converts a bare input to a string, but I can't figure out how to conditionally convert to a string or leave the string unmodified.

test_fun <- function(by) {
  # convert to enquo
  by1 <- rlang::enquo(by)

  # convert enquo to string
  by2 <- rlang::quo_text(by1)

  by2
}

# converts to string
test_fun(varname)

# not sure how to pass this unmodified
test_fun("varname")
like image 836
Daniel D. Sjoberg Avatar asked Jan 31 '26 06:01

Daniel D. Sjoberg


2 Answers

rlang::ensym() exists pretty much for this purpose, except its output is a name not a string, so you need to convert it.

test_fun <- function(by) {
  as.character(rlang::ensym(by))
}

test_fun(varname)
#> [1] "varname"

test_fun("varname")
#> [1] "varname"

Created on 2019-08-08 by the reprex package (v0.2.1)

I don't think it's necessarily bad to do so, foo <- "bar" and "foo" <- "bar" are equivalent, "head"(iris) and head(iris) are equivalent, ensym() makes it easy to have things like select(iris, "Species") and select(iris, Species) be equivalent. It's handy for interactive use, and if you want your function to be consistent with dplyr::select(), or even base::library() etc it would indeed be more surprising NOT to support this feature.

Just make sure that it makes sense in your use case as it could otherwise indeed be confusing.

If you want a deprecation warning you can use :

test_fun <- function(by) {
  if(is.character(rlang::enexpr(by)))
    warning("literal string input is deprecated, please use raw variable names")
  as.character(rlang::ensym(by))
}

test_fun(varname)
#> [1] "varname"

test_fun("varname")
#> Warning in test_fun("varname"): literal string input is deprecated, please use raw
#> variable names
#> [1] "varname"

Created on 2019-08-08 by the reprex package (v0.2.1)

like image 82
Moody_Mudskipper Avatar answered Feb 01 '26 20:02

Moody_Mudskipper


As noted, I strongly recommend against the practice of accepting multiple types if this creates ambiguity (and it does, here).

That said, the following does it:

test_fun = function (by) {
    by = substitute(by)
    if (is.name(by)) {
        as.character(by)
    } else if (is.character(by)) {
        by
    } else {
        stop('Unexpected type')
    }
}

Using rlang, in this case, doesn’t simplify the code.

like image 45
Konrad Rudolph Avatar answered Feb 01 '26 21:02

Konrad Rudolph