Как проверить, является ли аргумент функции допустимым именем?

Я делаю функцию, которая должна иметь возможность обрабатывать несколько классов для своего первого аргумента: формулы, символы, аккуратный выбор, имена переменных... Затем цель состоит в том, чтобы использовать аккуратный выбор с tidyselect::vars_select, за исключением голых формул.

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

Я нашел обходной путь с помощью tryCatch, который заключает в кавычки первый аргумент, если его оценка не удалась (и, следовательно, если он не существует в этой области).

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"

Мне это не кажется чистым, так как я ловлю все ошибки без фильтрации по моему конкретному случаю.

Есть ли встроенная функция для проверки этого или более чистое решение, чем этот обходной путь?


person Dan Chaltiel    schedule 21.03.2020    source источник
comment
Добавлен полный вывод foo. Сокращенные версии, которые у вас были изначально, немного вводили в заблуждение. Если то, что я вставил сейчас, не соответствует полученному результату, отредактируйте еще раз.   -  person dww    schedule 21.03.2020
comment
@dww мой плохой, твоя правка совершенно правильная.   -  person Dan Chaltiel    schedule 21.03.2020


Ответы (2)


Я думаю, что вы пытаетесь сделать следующее (используя здесь только базу 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"
person dww    schedule 21.03.2020
comment
Цель состоит в том, чтобы использовать tidyselection с tidyselect::vars_select, за исключением голых формул. Я уточнил это в вопросе. Ваш ответ интересен (я никогда не использую замену, поэтому никогда не думаю об этом), но, в конце концов, вам, похоже, тоже нужно tryCatch, поэтому мне это не кажется намного чище. Но, возможно, я чего-то не понял. - person Dan Chaltiel; 21.03.2020
comment
Извините, я думал, что вы хотели устранить сообщения об ошибках и предупреждения. Что и сделала моя первая попытка. Я изменил его, чтобы также исключить tryCatch, если это то, что вы подразумеваете под очистителем. Если этого все еще недостаточно tidyverse для ваших целей, то я удаляю - person dww; 21.03.2020

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

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"

Создана 21 июня 2020 г. с помощью пакета reprex (v0.3.0)

person TimTeaFan    schedule 20.06.2020