R: Замените переменные, связанные во всех родительских средах.

Функция base::substitute(expr, env), согласно странице документации,

возвращает дерево синтаксического анализа для (неоцененного) выражения expr, заменяя любые переменные, связанные в env.

Я ищу способ замены любых переменных, привязанных не к одной конкретной среде, а ко всем средам в текущем стеке вызовов, т. е. ко всем средам, встречающимся при переборе parent.frame(i), где i находится в seq_len(sys.nframe()). Кроме того, я хотел бы, чтобы применялись стандартные правила области действия.

Это противоречие: стандартная область видимости в R является лексической, но то, что я описываю здесь, является динамической областью видимости (спасибо @MikkoMarttila за помощь в прояснении этого). Что мне действительно нужно, так это способ замены любых переменных, связанных не в одной конкретной среде, а во всех родительских включающих средах, набор которых можно перечислить, многократно применяя base::parent.env().

Рассмотрим следующий пример:

do_something <- function(todo) {
  cat(
    paste(
      deparse(substitute(todo, environment())),
      collapse = "\n"
    )
  )
}

nested_do <- function() {

  var_2 <- "goodbye"

  do_something({
    print(var_1)
    print("world")
    print(var_2)
  })

}

var_1 <- "hello"

nested_do()

В настоящее время это дает

print(var_1)
print("world")
print(var_2)

где я хотел бы иметь

print("hello")
print("world")
print("goodbye")

Я просмотрел base::bquote() и rlang::enexpr(), но для обоих я должен явно пометить переменные для замены/отключения кавычек с помощью .() или !!. Я бы предпочел не указывать переменные вручную, а разрешать все найденное (как в base::substitute()). Кроме того, я попытался итеративно применить base::substitute() с соответствующими аргументами env, и я посмотрел на oshka::expand(), но ничего из того, что я пробовал, не делает то, что мне нужно.

Любая помощь очень ценится.

Дополнительный контекст

Я пытаюсь добиться следующего: я работаю над кластером, на котором работает LSF. Это означает, что я могу отправлять задания с помощью инструмента отправки bsub, который может принимать R-файл в качестве входных данных. Теперь я хотел бы иметь скрипт, который генерирует эти входные файлы (например, с помощью функции do_something()).

long_running_fun <- function(x) {
  Sys.sleep(100)
  x / 2
}

var_1 <- 2 + 2
var_2 <- var_1 + 10

do_something({
  print(var_1)
  var_3 <- long_running_fun(var_2)
  print(var_3)
})

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

print(4)
var_3 <- long_running_fun(14)
print(var_3)

person nbenn    schedule 19.07.2018    source источник
comment
Просто чтобы уточнить заголовок: я думаю, что номенклатура родительской среды здесь немного вводит в заблуждение, поскольку (насколько я понимаю) родителем среды выполнения является окружающая среда, которая часто отличается от вызывающей среды.   -  person Mikko Marttila    schedule 19.07.2018
comment
Может быть, я неправильно понимаю, но мне кажется, что желаемый вывод в дополнительном контексте несколько противоречит исходному вопросу? Изначально вы хотите подстановку, но здесь вы просто хотите зафиксировать выражение?   -  person Mikko Marttila    schedule 20.07.2018
comment
Мои извинения. Я бездумно скопировал и вставил желаемый результат. Это должно быть исправлено сейчас.   -  person nbenn    schedule 20.07.2018
comment
Итак, у вас есть фрагмент кода, который вы хотите отправить в качестве задания; но этот фрагмент кода зависит от текущего контекста, и у вас нет возможности передать контекст кластеру, который запускает фрагмент? (Правильно ли это?) Значит, вам нужно подставить всю информацию, необходимую из текущего контекста?   -  person Mikko Marttila    schedule 20.07.2018
comment
Да, звучит почти правильно.   -  person nbenn    schedule 20.07.2018


Ответы (3)


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

substitute_stack <- function(expr) {
  expr <- substitute(expr)

  # Substitute in all envs in the call stack
  envs <- rev(sys.frames())
  for (e in envs) {
    expr <- substitute_q(expr, e)
  }

  # sys.frames() does not include globalenv() and
  # substitute() doesnt "substitute" there
  e <- as.list(globalenv())
  substitute_q(expr, e)
}

# A helper to substitute() in a pre-quoted expression
substitute_q <- function(expr, env = parent.frame()) {
  eval(substitute(substitute(x, env), list(x = expr)))
}

Давайте попробуем:

do_something <- function(todo) {
  cat(
    paste(
      deparse(substitute_stack(todo)),
      collapse = "\n"
    )
  )
}

nested_do <- function() {
  var_2 <- "goodbye"

  do_something({
    print(var_1)
    print("world")
    print(var_2)
  })
}

var_1 <- "hello"

nested_do()
#> {
#>     print("hello")
#>     print("world")
#>     print("goodbye")
#> }

Является ли это на самом деле хорошей идеей, это совсем другой вопрос. подход, предложенный @G.Grothendieck, вероятно, будет предпочтительнее.

Создано 19 июля 2018 г. с помощью пакета reprex (v0.2.0.9000).

person Mikko Marttila    schedule 19.07.2018
comment
Ваше предложение работает для того, что я пытаюсь сделать (см. также раздел «Дополнительный контекст»). Что вам не нравится в вашем решении? Как вы думаете, почему решение @G.Grothendieck предпочтительнее? - person nbenn; 20.07.2018
comment
Ну, не то, чтобы мне это не нравилось, но пользоваться им нужно осторожно. По сути, это динамическая область видимости для выражения, поэтому вам нужно очень внимательно относиться ко всему, что находится в стеке вызовов, когда вы его используете: см., например. эту небольшую суть. - person Mikko Marttila; 20.07.2018
comment
Большое спасибо за дополнительный пример. У меня сложилось впечатление, что R использует динамическую область видимости. Однако R использует лексическую область видимости. Спасибо, что помогли мне развеять это заблуждение! Что я хочу, чтобы моя замена также применяла лексическую область видимости. Но это не должно быть проблемой, верно? Список сред для повторения должен быть изменен, но в остальном все должно быть в порядке, не так ли? Я попытаюсь. - person nbenn; 20.07.2018
comment
Ага. rlang::env_parents() может пригодиться. - person Mikko Marttila; 20.07.2018

Вместо этого я предлагаю вам просто передать среду следующим образом:

esubstitute <- function(expr, envir) do.call("substitute", list(expr, envir))

do_something <- function(todo, envir = parent.frame()) {
  cat(
    paste(
      deparse(esubstitute(todo, envir)),
      collapse = "\n"
    )
  )
}

nested_do <- function(envir = parent.frame()) {

  var_2 <- "goodbye"

  do_something({
    print(var_1)
    print("world")
    print(var_2)
  }, envir)

}

var_1 <- "hello"

nested_do()

давая:

[1] "hello"
[1] "world"
[1] "goodbye"
"goodbye"> 

Вы также можете взглянуть на пакет envnames.

person G. Grothendieck    schedule 19.07.2018
comment
Спасибо, что решили мою проблему. К сожалению, я не вижу, как это мне помогает. Возможно, мой вопрос был неясен: я пытаюсь добиться следующего: 1) захватить выражение, 2) заменить любые переменные, связанные в текущей или родительской среде, и 3) распечатать исходное выражение с замещенными переменными (или записать в файл или еще). Чтобы примерно понять, что вы делаете, я могу просто оценить todo. Это работает, так как все переменные находятся в области видимости. Или я что-то упускаю? - person nbenn; 19.07.2018
comment
Это было ясно, но я не думаю, что это хорошая идея. Было бы лучше попытаться максимально придерживаться того, как работает R, и это делает это, давая требуемый ответ на пример кода. - person G. Grothendieck; 20.07.2018
comment
Если хотите, не могли бы вы пояснить, почему вы считаете то, что я пытаюсь сделать, плохой идеей? Я бы очень хотел как можно больше придерживаться того, как работает R, но я не понимаю, как ваше решение может работать для меня (ознакомьтесь с разделом «Дополнительный контекст»). Я все еще думаю, что мое истинное намерение могло быть неясным с самого начала. Не поймите меня неправильно, я ценю вашу попытку помочь мне. - person nbenn; 20.07.2018
comment
Будет сложно отладить, если что-то пойдет не так, так как он ищет везде. Кроме того, если вы реализуете его с помощью итеративной замены, вы можете получить замену на замены, что действительно странно. - person G. Grothendieck; 20.07.2018
comment
Итак, вы говорите, что то, чего я хочу добиться, невозможно без использования странных хаков, которые будет трудно отладить, если что-то пойдет не так и в конечном итоге окажется действительно странным? Извините, что немного искажаю ваши слова ;-) Вы видите, что мое собственное решение легко ломается? У вас есть пример, когда мое решение разваливается? - person nbenn; 20.07.2018
comment
Я имел в виду написание программного обеспечения, которое его использует. Я думаю, что это программное обеспечение было бы трудно отлаживать, поскольку вы никогда не знаете, откуда берутся объекты. Это та же проблема, что и при использовании большого количества глобальных переменных. Также, как указано, итеративная схема означает, что если выражение a заменяется на b, то на следующей итерации b может быть заменено на c, поэтому вы непреднамеренно получаете несколько уровней косвенности. - person G. Grothendieck; 20.07.2018
comment
Ты был прав. Оказалось, что это сложно надежно и предсказуемо использовать. В любом случае спасибо. - person nbenn; 02.08.2018

Основываясь на @MikkoMarttila answer, я думаю, что следующее делает то, что я просил

do_something <- function(todo) {

  # A helper to substitute() in a pre-quoted expression
  substitute_q <- function(expr, env) {
    eval(substitute(substitute(x, env), list(x = expr)))
  }

  substitute_parents <- function(expr) {
    expr <- substitute(expr)

    # list all parent envs
    envs <- list()
    env <- environment()
    while (!identical(env, globalenv())) {
      envs <- c(envs, env)
      env <- parent.env(env)
    }
    # substitute in all parent envs
    for (e in envs) {
      expr <- substitute_q(expr, e)
    }

    # previously did not include globalenv() and
    # substitute() doesnt "substitute" there
    e <- as.list(globalenv())
    substitute_q(expr, e)
  }

  cat(
    paste(
      deparse(substitute_parents(todo)),
      collapse = "\n"
    )
  )
}

Это дает

nested_do <- function() {
  var_2 <- "not_this"

  do_something({
    print(var_1)
    Sys.sleep(100)
    print("world")
    print(var_2)
  })
}

var_1 <- "hello"
var_2 <- "goodbye"

do_something({
  print(var_1)
  Sys.sleep(100)
  print("world")
  print(var_2)
})
#> {
#>     print("hello")
#>     Sys.sleep(100)
#>     print("world")
#>     print("goodbye")
#> }
nested_do()
#> {
#>     print("hello")
#>     Sys.sleep(100)
#>     print("world")
#>     print("goodbye")
#> }
person nbenn    schedule 20.07.2018