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

你。 提交于 2020-06-12 08:40:26

问题


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")

回答1:


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.




回答2:


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)




回答3:


I agree with @Konrad's comment but you can easily do this with base R:

test_fun <- function(by) {

  res <- substitute(by)

  if (is.character(res)) return(res)
  if (is.name(res)) return(deparse(res))

  stop("unsupported input")
}

test_fun(varname)
#[1] "varname"

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

test_fun(y ~ x)
#Error in test_fun(y ~ x) : unsupported input


来源:https://stackoverflow.com/questions/57376099/accept-both-bare-from-rlang-or-string-as-a-function-input

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!