Тонкое разное поведение между() и attach() в R?

Люди часто используют функции attach() и detach() для настройки "путей поиска" для имен переменных в R, но поскольку это изменяет глобальное состояние, которое трудно отслеживать, люди рекомендуют вместо with(), которая устанавливает временное изменение пути поиска для продолжительности одного выражения.

Однако я только заметил, что в отличие от attach(), with(), по-видимому, не выполняет "разрешить" функции. Например, сначала настройте фиктивную функцию, которая будет обращаться к переменной с именем x:

f <- function { print(x) }

Теперь,

with(list(x=42), f())

не удается, даже если

with(list(x=42), print(x))

и

attach(list(x=42))
f()

оба успеха!: (

Может ли кто-нибудь сказать мне, почему? Я бы хотел, чтобы with() вел себя точно так же, как attach() здесь, чтобы я мог эффективно передать большой список параметров функции, настроив среду, содержащую значения параметров, с помощью with(). Я рассматриваю этот подход как имеющее несколько преимуществ по сравнению с альтернативами (два из них, которые я рассмотрел, (а) кропотливо передают все параметры в функцию и (б) явно передают в список/кадр параметров как аргумент функции и имеют сама функция вызывает with()), но она не работает. Я нахожу это несоответствие довольно тревожным, чтобы быть честным! Любое объяснение/помощь будут оценены.

Я использую R 2.11.1.

Ответ 1

Разница между тем, что делает with(list(x = 42), f()), и то, что вы ожидаете, - это разница между лексическим охватом (что и использует R) и динамическим охватом (что, кажется, то, что вы ожидаете).

Лексическая область видимости означает, что свободные переменные (например, переменная x в f) просматриваются в среде, где f определен, а не из среды f вызывается из.

f определяется в глобальной среде, так что там, где x просматривается.

Не имеет значения, что with был вызван для создания новой среды, из которой вызывается f, поскольку среда, из которой ее вызов не участвует в поиске свободных переменных.

Чтобы заставить это работать так, как вы хотите создать копию f и reset своей среды, поскольку это то, что R использует для поиска свободных переменных:

with(list(x = 42), { environment(f) <- environment(); f() })

По общему признанию, это немного обременительно, но вы можете немного упростить его с помощью прото-пакета, поскольку proto сбрасывает среду каждой функции, которая явно вставлена ​​в прото-объект:

library(proto)
with(proto(x = 42, f = f), f())

ДОБАВЛЕНО:

Обратите внимание, что если ваша цель состоит в том, чтобы делать объектно-ориентированное программирование (в соответствии с вашим комментарием к другому ответу), вы можете захотеть заглянуть в proto далее в proto домашняя страница. Например, мы могли бы определить прото-объект p и переопределить f так, чтобы его метод p (в этом случае он должен принять объект в аргументе 1) следующим образом:

library(proto)
p <- proto(x = 42, f = function(.) print(.$x))
p$f()

ДОБАВЛЕНО 2:

При подключенном случае запуск f() сначала выполняется в глобальной среде, так как здесь определяется f. Поскольку x не найден в глобальной среде, он смотрит на родителя глобальной среды, и в этом случае он находит его там. Мы можем обнаружить родителя глобальной среды с помощью parent.env, и здесь мы видим, что присоединенная среда стала родительским элементом глобальной среды.

> attach(list(x = 42))
> parent.env(.GlobalEnv)
<environment: 0x048dcdb4>
attr(,"name")
[1] "list(x = 42)"

Мы можем просмотреть глобальную среду и всех ее предков в следующем порядке:

> search()
 [1] ".GlobalEnv"        "list(x = 42)"      "package:stats"    
 [4] "package:graphics"  "package:grDevices" "package:utils"    
 [7] "package:datasets"  "package:methods"   "Autoloads"        
[10] "package:base"   

Таким образом, "list(x = 42)" является родительским элементом глобальной среды, stats является родительским элементом "list(x = 42)" и т.д.

Ответ 2

Я думаю, это связано с тем, что вы не определяете какие-либо аргументы для f, а оттуда как требуется поиск x для print(x).

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

Внутри with() происходит то, что любые аргументы, необходимые для f, будут взяты из аргумента data. Но поскольку ваш f не принимает никаких аргументов, x в списке никогда не используется. Вместо этого R возвращается к обычному поведению и ищет x в среде f, глобальной среды, и, конечно же, ее нет. Разница с attach() заключается в том, что она явно добавляет объект, содержащий x к пути поиска, который находится в глобальной среде.

Если вы правильно напишете свою функцию, следуя мантре, которую вы передаете любым и всем аргументам, которые вы используете в функции, тогда все работает так, как можно было бы ожидать:

> F <- function(x) print(x)
> with(list(x = 42), F(x))
[1] 42
> ls()
[1] "f" "F"

Если у вас уже есть список или аргументы, необходимые для вызова, возможно, рассмотрите do.call(), чтобы настроить вызов для вас, вместо использования с. Например:

> do.call(F, list(x = 42))
[1] 42

Вам все еще нужно, чтобы ваша функция была правильно определена с помощью аргументов, так как ваш f не работает:

> do.call(f, list(x = 42))
Error in function ()  : unused argument(s) (x = 42)

Ответ 3

В описании из ?with указано:

Оцените выражение R в среде, построенной из данных, возможно изменение исходных данных.

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

> f <- function() print(x)
> f()
Error in print(x) : object 'x' not found
> with(list(x=42),f())
Error in print(x) : object 'x' not found
> x <- 13
> f()
[1] 13
> with(list(x=42),f())
[1] 13
> f2 <- function(x) print(get("x",parent.frame()))
> f2()
[1] 13
> with(list(x=42),f2())
[1] 42

Чтобы прояснить, как обычно используется with, данные передаются функции в среде with, и обычно вызываемая функция обычно не называется глобальной.

Ответ 4

Список параметров передачи работает для меня:

f <- function(params) with(params, {
    print(x)
})
f(list(x=42))
# [1] 42

Но вы должны рассмотреть явные рефренсы, например:

f <- function(params) {
    print(params$x)
}

Причина в стадии разработки, с большим количеством переменных, это вопрос времени, когда вы делаете что-то вроде:

f <- function(params) with(params, {
    # many lines of code 
    print(x)
})
x <- 7
f(list(y=8))
# [1] 7 # wasn't in params but you got an answer