Нахождение среды происхождения аргументов вызова (точки)

Я хочу найти среду, из которой исходят ... (точки) аргументы вызова.

Сценарий

Например, рассмотрим функцию

foo <- function(x, ...) {
  # do something
}

Нам нужна функция env_dots(), которую мы вызываем из foo(), которая находит исходную среду ... при вызове foo(), даже когда вызов foo() глубоко вложен. То есть, если мы определим

foo <- function(x, ...) {
  # find the originating environment of '...'
  env <- env_dots()

  # do something
}

и вложите вызов foo, например,

baz <- function(...) {
  a <- "You found the dots"
  bar(1, 2)
}
bar <- function(...)
  foo(...)

затем вызов baz() должен возвращать среду, в которой происходит ... во (вложенном) вызове foo(...): это среда, в которой выполняется вызов bar(1, 2), поскольку 2 (но не 1) передается точкам foo. В частности, мы должны получить

baz()$a
#> [1] "You found the dots"

Наивная реализация env_dots()

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

Вот одна возможность для env_dots():

# mc: match.call() of function from which env_dots() is called
env_dots <- function(mc) {
  # Return NULL if initial call invokes no dots
  if (!rlang::has_name(mc, "...")) return(NULL)

  # Otherwise, climb the call stack until the dots origin is found
  stack <- rlang::call_stack()[-1]
  l <- length(stack)
  i <- 1
  while (i <= l && has_dots(stack[[i]]$expr)) i <- i + 1
  # return NULL if no dots invoked
  if (i <= l) stack[[i + 1]]$env else NULL
}

# Does a call have dots?
has_dots <- function(x) {
  if (is.null(x))
    return(FALSE)
  args <- rlang::lang_tail(x)
  any(vapply(args, identical, logical(1), y = quote(...)))
}

Кажется, это работает: с

foo <- function(x, ...)
  env_dots(match.call(expand.dots = FALSE))

мы получаем

baz()$a
#> [1] "You found the dots"

bar(1, 2)  # 2 gets passed down to the dots of foo()
#> <environment: R_GlobalEnv>

bar(1)     # foo() captures no dots
#> NULL

Вопросов

Вышеупомянутая реализация env_dots() не очень эффективна.

  1. Есть ли более эффективный способ реализовать env_dots() в rlang и / или base R?

  2. Как я могу переместить вызов match.call() в env_dots()? match.call(sys.function(-1), call = sys.call(-1), expand.dots = FALSE) действительно будет работать.

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


person egnha    schedule 27.06.2017    source источник


Ответы (1)


Извините, что откопал старый вопрос, но я не уверен, что желаемое поведение четко определено. ... - это не одно выражение; это список выражений. В случае rlang quosures каждое из этих выражений имеет свое собственное окружение. Итак, какой должна быть среда списка?

Кроме того, сам список ... может быть изменен. Рассмотрим следующий пример, где g берет свое ..., добавляет к нему (неоцененное) выражение x+3 и передает его в f.

f <- function(...) {rlang::enquos( ... )}

g <- function(...) {
  a <- rlang::quo( x + 3 )
  l <- rlang::list2( a, ... )
  f(!!!l)
}

b <- rlang::quo( 5 * y )

g( b, 10 )
# [[1]]
# <quosure>
#   expr: ^x + 3
#   env:  0x7ffd1eca16f0

# [[2]]
# <quosure>
#   expr: ^5 * y
#   env:  global

# [[3]]
# <quosure>
#   expr: ^10
#   env:  empty

Обратите внимание, что у каждого из трех вопросов, попавших в f, есть своя собственная среда. (Как вы отметили в своем вопросе, такие литералы, как 10, имеют пустую среду. Это связано с тем, что значение не зависит от того, в какой среде оно оценивается.)

Учитывая этот сценарий, что должно возвращать гипотетическое env_dots() при вызове внутри f()?

person Artem Sokolov    schedule 23.10.2018
comment
Да, я считаю, что вы правы. Проблема некорректно поставлена. Спасибо. - person egnha; 23.10.2018