Принимайте как голые (из rlang), так и строковые значения в качестве входных данных

Я редактирую существующую функцию в пакете. В настоящее время функция принимает имя столбца во фрейме данных в виде строки. Я обновляю функцию, чтобы принимать либо строковое имя, либо простое имя. Но я столкнулся с некоторыми проблемами.

Общий подход, который я хотел бы использовать, - это преобразовать пустую строку в строку, поэтому остальную часть функции не нужно обновлять. Если пользователь передает имя строкового столбца, мне не нужно изменять ввод.

Приведенный ниже код преобразует простой ввод в строку, но я не могу понять, как условно преобразовать в строку или оставить строку неизмененной.

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

person Daniel D. Sjoberg    schedule 06.08.2019    source источник
comment
Я настоятельно рекомендую не делать этого, поскольку это неизбежно приводит к двусмысленности. Сделайте свои API категоричными и недвусмысленными, то есть принимайте либо имена, либо строки, но не то и другое одновременно.   -  person Konrad Rudolph    schedule 06.08.2019
comment
Спасибо за вклад @KonradRudolph! Мне нравится это предложение. Имея это в виду, есть ли способ потребовать простой ввод, но распечатать примечание о том, что строковый ввод устарел? Это пакет, который используется многими в моей компании, и если я внесу изменения в API, я хотел бы, чтобы это изменение было как можно более четким.   -  person Daniel D. Sjoberg    schedule 06.08.2019
comment
На самом деле предупреждение об устаревании - это прилично, и я кое-что делал сам в прошлом.   -  person Konrad Rudolph    schedule 06.08.2019


Ответы (3)


Как уже отмечалось, я настоятельно рекомендую не принимать несколько типов, если это создает двусмысленность (и это действительно так).

Тем не менее, это делает следующее:

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

В этом случае использование rlang не упрощает код.

person Konrad Rudolph    schedule 06.08.2019

rlang::ensym() существует в значительной степени для этой цели, за исключением того, что его вывод - это имя, а не строка, поэтому вам нужно преобразовать его.

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

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

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

Создано 08.08.2019 с помощью пакета REPEX (v0.2.1)

Я не думаю, что это обязательно плохо, foo <- "bar" и "foo" <- "bar" эквивалентны, "head"(iris) и head(iris) эквивалентны, ensym() упрощает эквивалентность таких вещей, как select(iris, "Species") и select(iris, Species). Это удобно для интерактивного использования, и если вы хотите, чтобы ваша функция согласовывалась с dplyr::select() или даже base::library() и т. Д., Было бы более удивительно НЕ поддерживать эту функцию.

Просто убедитесь, что это имеет смысл для вашего варианта использования, иначе это действительно может сбить с толку.

Если вам нужно предупреждение об устаревании, вы можете использовать:

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"

Создано 08.08.2019 с помощью пакета REPEX (v0.2.1)

person Moody_Mudskipper    schedule 08.08.2019
comment
Это было бы моим предпочтительным решением, поскольку rlang::ensym() стандартизирует ввод. Единственное, что нужно добавить, это то, что в зависимости от того, что OP хочет делать ниже по потоку, может не быть необходимости преобразовывать символ в строку (например, dplyr функции понимают и строки, и символы). При работе с несколькими аргументами иногда еще проще перенаправить их прямо в _3 _ без каких-либо модификаций самостоятельно. - person Artem Sokolov; 08.08.2019

Я согласен с комментарием @ Konrad, но вы можете легко сделать это с помощью базы 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
person Roland    schedule 06.08.2019