Работа с пропущенными значениями в аргументах с многоточием в R

Как вы справляетесь с пропущенными значениями в аргументе ... функции?

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

Проблема в том, что когда я использую list(...), отсутствующие значения вызывают ошибку:

foo = function(...){
  list(...)
}

bar = function(name){
  foo(name=name)
}

bar("a")
#> $name
#> [1] "a"
bar()
#> Error in foo(name = name): argument "name" is missing, with no default

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

Я просматривал квазоры в rlang, но is_missing и quo_is_missing нет кажется, работает на ....

library(rlang)

foo = function(...){
  lapply(rlang::enquos(...), is_missing)
}

bar("a")
#> $name
#> [1] FALSE
bar()
#> $name
#> [1] FALSE

foo = function(...){
  lapply(rlang::enquos(...), quo_is_missing)
}

bar("a")
#> $name
#> [1] FALSE
bar()
#> $name
#> [1] FALSE

И tidy_eval вылетает с той же ошибкой:

foo = function(...){
  lapply(rlang::enquos(...), eval_tidy)
}

bar("a")
#> $name
#> [1] "a"
bar()
#> Error in FUN(X[[i]], ...): argument "name" is missing, with no default

Редактировать

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

library(rlang)
fn <- function(arg) enquo(arg)
fn()
#> <quosure>
#> expr: ^
#> env:  empty
quo_is_missing(fn())
#> [1] TRUE

person Duccio A    schedule 11.01.2021    source источник
comment
Что вы хотите получить, когда вы вызываете bar() без аргументов? Вы можете установить значение по умолчанию для имени: bar = function(name=NA){foo(name=name)}.   -  person MrFlick    schedule 11.01.2021
comment
имя является обязательным аргументом с тем, как вы определили панель   -  person rawr    schedule 11.01.2021
comment
Редактирование не стало более четким. Что вы хотите, чтобы bar() вернул? В настоящее время вы жестко кодируете параметр name=name. Вы не можете передать отсутствующее значение x в list(name=x). Итак, вы хотите иметь именованный параметр или нет?   -  person MrFlick    schedule 12.01.2021
comment
Привет @MrFlick, я хотел бы иметь возможность выполнить проверку is_missing по списку необязательных аргументов .... foo — это функция, содержащая некоторый общий код для функций с явными аргументами, но с разными именами. Идеально. Я хотел бы иметь возможность проверять отсутствующие аргументы в foo, а не в bar. Теперь enquo(...) создает список без ошибок, когда некоторые значения отсутствуют, но когда вы проверяете отсутствующие значения в результирующем списке, missing и quo_is_missing не могут их обнаружить.   -  person Duccio A    schedule 12.01.2021


Ответы (2)


Как говорится в комментариях, проблема не в list(...), а в bar(name). Вы определили функцию, которая принимает аргумент, и вызываете эту функцию без каких-либо аргументов, отсюда и ошибка.

Если вы хотите, чтобы bar() работало, даже если аргумент name отсутствует, вы можете использовать missing():

bar = function(name){
  if(missing(name)) {
    foo()
  } else {
    foo(name=name)
  }
}

И теперь bar() работает:

> bar()
list()

После редактирования: похоже, проблема в том, что list(...) заставляет оценивать name, а затем видит, что bar() не видел никаких name. Может быть, в вашем случае имеет смысл перехватить вызов родительской функции bar()?:

foo = function(...) {
  as.list(sys.call(1))[-1]
}

bar = function(name){
  foo(name=name)
}

> bar()
list()

> bar(name="a")
$name
[1] "a" 
person Karolis Koncevičius    schedule 11.01.2021
comment
Спасибо за ответ. Я понимаю, что вы можете работать с отсутствующими значениями с помощью missing, но это не то, о чем я прошу. Я отредактировал вопрос, надеюсь, редактирование прояснит мой вопрос. - person Duccio A; 12.01.2021
comment
Я думаю, что теперь я более или менее вижу проблему, написал альтернативный ответ ниже. - person Karolis Koncevičius; 12.01.2021
comment
@DuccioA ping, чтобы проверить, соответствует ли новое предложение тому, что вам нужно. - person Karolis Koncevičius; 12.01.2021
comment
Спасибо за редактирование и извините за задержку, я работал над чем-то другим и не смог протестировать решение. Проблема с этим решением заключается в том, что если я укажу аргументы по положению, а не по имени в bar, я потеряю имя аргумента, например: bar("a"). В итоге я написал функцию, которая перебирает ... и превращает отсутствующие значения в NULL, используя tryCatch. В любом случае, спасибо за ответ, я понимаю, что мой вопрос не очень ясен. - person Duccio A; 27.01.2021

Я нашел способ справиться со своей проблемой. Это не настоящее решение вопроса, а скорее обходной путь. Он использует tryCatch для преобразования отсутствующих значений в ... в NULL. Я опубликую это здесь, если кто-то найдет это полезным.

library(rlang)
ellipsis_to_list = function(...){
  args_names = call_args_names(match.call())
  args_list = list()
  range = 0:...length()
  range = range[-1]
  for(i in seq_along(range)){
    v = tryCatch(...elt(i),
                 error=function(e){
                   if(grepl("missing", e$message)) NULL
                   else stop(e$message)
                 })
    args_list = c(args_list, list(v))
  }
  names(args_list) = args_names
  args_list
}

foo = function(...){
  args_list = ellipsis_to_list(...)
  args_list[ ! sapply(args_list, is.null)]
}

boo = function(name){
  foo(name=name)
}

boo("a")
#> $name
#> [1] "a"
boo()
#> named list()

Создано 27 января 2021 г. с помощью пакета reprex (v0.3.0)

person Duccio A    schedule 27.01.2021