Подтвердить что ты не робот

Назначение для замены значения в нелокальном списке

[[<- ведет себя по-разному для списков и сред при использовании на нелокальных объектах:

lst = list()
env = new.env()

(function () lst[['x']] = 1)()
(function () env[['x']] = 1)()
lst
# list()

as.list(env)
# $x
# [1] 1

Другими словами, если целью [[<- является среда, она изменяет (нелокальную) среду, но если ее вектор/список создает новый локальный объект.

Я хотел бы знать две вещи:

  • Почему это различие в поведении?
  • Есть ли способ добиться того же результата для списков, что и для сред, без использования <<-?

Что касается (1), мне известно о множественных различиях между списками и средами (в частности, я знаю, что среды не копируются), но в документации не упоминается, почему семантика [[<- отличается между двумя - в частности, почему оператор будет работать в разных сферах. Это ошибка? Его контринтуитивный, по крайней мере, и требует некоторых нетривиальных махинаций реализации. 1

Что касается (2), очевидное решение, конечно, должно использовать <<-:

(function () lst[['x']] <<- 1)()

Однако я предпочитаю понимать разницу строго, а не просто обойти их. Кроме того, Ive до сих пор использовал assign вместо <<-, и я предпочитаю это, так как он позволяет мне больше контролировать область назначения (в частности, поскольку я могу указать inherits = FALSE. <<- слишком много voodoo для мой вкус.

Однако вышеупомянутое не может быть разрешено (насколько мне известно) с помощью assign, потому что assign работает только в средах, а не в списках. В частности, в то время как assign('x', 1, env) работает (и делает то же, что и выше), assign('x', 1, lst) не работает.


1 Чтобы разработать, конечно, ожидалось, что R делает разные вещи для разных типов объектов, используя динамическую отправку (например, через S3). Однако здесь это не так (по крайней мере, не напрямую): различие в разрешении области происходит до того, как будет известен тип объекта целевого назначения - в противном случае вышеуказанное будет работать с глобальным lst, а не создавать новый локальный объект. Поэтому внутренне [[<- должен выполнять эквивалент:

`[[<-` = function (x, i, value) {
    if (exists(x, mode = 'environment', inherits = TRUE))
        assign(i, value, pos = x, inherits = FALSE)
    else if (exists(x, inherits = FALSE)
        internal_assign(x, i, value)
    else
        assign(x, list(i = value), pos = parent.frame(), inherits = FALSE)
}
4b9b3361

Ответ 1

Определение R-языка (раздел 2.1.10) гласит:

В отличие от большинства других объектов R, среда не копируется при передаче к функциям или используется в назначениях.

Раздел "6.3 Подробнее об оценке" также дает немного полезный намек:

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

eval(quote(total <- 0), environment(robert$balance)) # rob Rob

Это также верно при оценке в списках, но исходный список не меняется, потому что на самом деле работает над копией.

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

Относительно вашего второго вопроса:

Если вы работаете со списком, единственным вариантом является

  • скопировать список в локальную область (используя get),
  • назначить в список
  • используйте assign, чтобы скопировать измененный список обратно в исходную среду.