Я пытаюсь быстро проверить список аргументов функции, каждый из которых принимает вектор строгих значений. Я могу сделать это по одному:
fn <- function(x = c("foo", "bar"), y = c("bar", "baz")) {
rlang::arg_match(x)
rlang::arg_match(y)
}
fn(x = "bar", y = "bar")
# [1] "bar"
fn(x = "baz", y = "baz")
# Error in `fn()`:
# ! `x` must be one of "foo" or "bar", not "baz".
# ℹ Did you mean "bar"?
# Run `rlang::last_trace()` to see where the error occurred.
fn(x = "bar", y = "foo")
# Error in `fn()`:
# ! `y` must be one of "bar" or "baz", not "foo".
# Run `rlang::last_trace()` to see where the error occurred.fn(x = "baz", y = "baz")
Но когда я пытаюсь объединить их с помощью lapply, переменные передаются как вызовы, а не как символы.
fn <- function(x = c("foo", "bar"), y = c("bar", "baz")) {
lapply(c(x, y), function(i) rlang::arg_match(enquo(i)))
}
fn(x = "bar", y = "bar")
# Error in `rlang::arg_match()`:
# ! `arg` must be a symbol, not a call.
# Run `rlang::last_trace()` to see where the error occurred.
fn <- function(x = c("foo", "bar"), y = c("bar", "baz")) {
lapply(c("x", "y"), function(i) rlang::arg_match(get(i)))
}
fn(x = "bar", y = "bar")
# Error in `rlang::arg_match()`:
# ! `arg` must be a symbol, not a call.
# Run `rlang::last_trace()` to see where the error occurred.
fn <- function(x = c("foo", "bar"), y = c("bar", "baz")) {
lapply(c("x", "y"), function(i) rlang::arg_match(eval(i)))
}
fn(x = "bar", y = "bar")
# Error in `rlang::arg_match()`:
# ! `arg` must be a symbol, not a call.
# Run `rlang::last_trace()` to see where the error occurred.
fn <- function(x = c("foo", "bar"), y = c("bar", "baz")) {
lapply(c("x", "y"), function(i) rlang::arg_match({{ i }}))
}
fn(x = "bar", y = "bar")
# Error in switch(type, call = "prefix", control = , delim = , subset = "special", :
# EXPR must be a length 1 vector
fn <- function(x = c("foo", "bar"), y = c("bar", "baz")) {
lapply(c(x, y), function(i) rlang::arg_match({{ i }}))
}
fn(x = "bar", y = "bar")
# Error in switch(type, call = "prefix", control = , delim = , subset = "special", :
# EXPR must be a length 1 vector
Как передать их, не звоня им? Или есть более эффективный способ проверить, что каждый из подмножеств аргументов имеет приемлемое значение?
Ни rlang::arg_match
, ни base::match.arg
не работают хорошо в lapply
, потому что контекст функции меняется.
Вы можете обойти части, используя цикл for с некоторой ерундой eval.
fn <- function(x = c("foo", "bar"), y = c("bar", "baz")) {
for(v in c("x", "y")) {
eval(rlang::expr(rlang::arg_match(!!(rlang::sym(v)))))
}
}
Или вы можете просто бросить свою собственную шашку
fn <- function(x = c("foo", "bar"), y = c("bar", "baz")) {
for(v in c("x", "y")) {
expected <- eval(formals(sys.function())[[v]])
if (!get(v) %in% expected) stop(paste0("'", v, "' must be one of ", toString(sQuote(expected, F)), " not '", get(v), "'"))
}
}
Более общим решением будет такой помощник, как
check_vars <- function(vars, env = parent.frame(), fn = sys.function(1)) {
if (any(!vars %in% names(formals(fn)))) {
message("cannot check values for: ", toString(vars[!vars %in% names(formals(fn))]))
vars <- intersect(vars, names(formals(fn)))
}
expected <- lapply(formals(fn)[vars], rlang::eval_bare)
for(v in vars) {
obs <- get(v, envir=env)
if (!obs %in% expected[[v]]) {
stop(paste0("'", v, "' must be one of ", toString(sQuote(expected[[v]], F)), " not '", obs, "'"), call.=F)
}
}
}
Который вы можете повторно использовать в нескольких функциях с помощью
fn <- function(x = c("foo", "bar"), y = c("bar", "baz")) {
check_vars(c("x", "y"))
}
Наша вспомогательная функция позволяет нам явно передавать среду функции и вызывающую функцию. Это позволит нам переопределить любой из них при необходимости. Это означает, что вы можете использовать его с lapply
fn <- function(x = c("foo", "bar"), y = c("bar", "baz")) {
lapply(c("x", "y"), check_vars, env=environment(), fn=sys.function(1))
}