Почему R не ищет указанный объект в предоставленном родительском дереве среды? - PullRequest
1 голос
/ 03 мая 2020

Конфигурация:

OS : Windows 10 (64 bits)
R version: 3.6.3

Я изучаю R, и в настоящее время я читаю об окружениях в R. Я практиковался и придумал пример, который Я создал себя, но кажется, что я все еще не могу объяснить и понять концепцию поиска объектов в R должным образом. Вообще говоря, я до сих пор понял (пожалуйста, поправьте меня, если я ошибаюсь), что если R не находит объект в текущей среде, он вызывает в порядке всех существующих родительских сред. Чтобы увидеть, как это работает на практике, я создал следующую программу:

library(rlang)
library(envnames)
library(lobstr)
e1 <- env()
e2 <- new_environment(parent = e1)
e3 <- new_environment(parent = e2)
e4 <- new_environment(parent = e3)
e5 <- new_environment(parent = e4)
e6 <- new_environment(parent = e5)
e7 <- new_environment(parent = e6)
e8 <- new_environment(parent = e7)
e9<- new_environment(parent = e8)
e10 <- new_environment(parent = e9)
e4$testvar <- 1200
e10$testfun <- function(x) {
    print(envnames::environment_name(caller_env()))
    return (testvar)
}

И вот как я запускаю вышеупомянутую программу, выбрав e10 в качестве среды вызывающей стороны

with(data = e10, expr = e10$testfun())

Учитывая что testvar определен в среде e4 и e4 является предком e10 , я ожидал, что R поднимается вверх в дереве родителей от e10 до e4, чтобы найти значение testvar. Но программа останавливается со следующей ошибкой:

Error in e10$testfun() (from #3) : object 'testvar' not found

Не могли бы вы сказать, что я неправильно понял? Тот факт, что я использую with(data = e10, ...), не должен означать, что среда, используемая для вызова функции, будет e10?

Ответы [ 2 ]

2 голосов
/ 03 мая 2020

Итак, это необычно нюансированная проблема. Здесь вам нужно подумать о двух типах сред: среда binding или среда, имеющая привязку к вашей функции, и среда , включающая , или среда где ваша функция была создана. В этом случае среда привязки - e10, но среда , включающая , является глобальной средой. Начиная с Advanced Hadley Wickham's R :

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

Рассмотрим следующее (выполненное после выполнения предоставленного вами кода), демонстрирующее это:

eval(expression(testfun()), envir = e10)
# [1] "e10"
# Error in testfun() : object 'testvar' not found
testvar <- 600
eval(expression(testfun()), envir = e10)
# [1] "e10"
# [1] 600

Более того, теперь рассмотрим:

eval(envir = e10, expr = expression(
    testfun2 <- function(x) {
        print(envnames::environment_name(caller_env()))
        return (testvar)
    }
))
eval(expression(testfun2()), envir = e10)
# [1] "e10"
# [1] 1200

Надеюсь, это прояснит проблему.

Обновление: определение окружающих и связывающих сред

Итак, как мы можем определить связывающие и окружающие среды для таких функций, как testfun()?

Как G. Ответ Гротендика показывает , функция environment() дает вам окружение для функции:

environment(e10$testfun)
# <environment: R_GlobalEnv>

Насколько мне известно, в базе R нет простой функции, которая давала бы вам привязку функции сред. Если искомая функция находится в родительской среде, вы можете использовать pryr::where():

pryr::where("mean")
# <environment: base>

(есть функция base, чтобы проверить, находится ли функция в среде, exists() и pryr::where() использует его. Но он не распространяется через родительские среды, такие как where().)

Однако, если вам приходится искать в дочерних средах, насколько мне известно, это не так. функция для этого. Но, кажется, довольно просто издеваться над ним:

get_binding_environments <- function(fname) {
    ## First we need to find all the child environments to search through.
    ## We don't want to start from the execution environment;
    ## we probably want to start from the calling environment,
    ## but you may want to change this to the global environment.
    e <- parent.frame()
    ## We want to get all of the environments we've created
    objects <- mget(ls(envir = e), envir = e)
    environments <- objects[sapply(objects, is.environment)]
    ## Then we use exists() to see if the function has a binding in any of them
    contains_f <- sapply(environments, function(E) exists(fname, where = E))
    return(unique(environments[contains_f]))
}

get_binding_environments("testfun")
# [[1]]
# <environment: 0x55f865406518>

e10
# <environment: 0x55f865406518>
1 голос
/ 03 мая 2020

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

environment(e10$testfun)
## <environment: R_GlobalEnv>

Когда функция ищет свободную переменную (которая указана, но не определена в функции), например testvar, она использует среду функции (и ее предков). ) найти переменную. testvar находится в e4, но e4 не является предком глобальной среды, поэтому в вопросе testvar не найдено.

Другие среды не имеют значения. Если функция вызывается, то среда вызывающей стороны (также известная как родительский фрейм) не играет никакой роли в поиске переменных. Точно так же, если функция позже помещается где-то еще (в данном случае e10), эта среда также не играет роли в поиске переменных. Кроме того, следует понимать, что когда функция, о которой идет речь, помещается в e10, она уже была определена в глобальной среде, и поэтому глобальная среда уже была установлена ​​в качестве ее среды. Функция состоит из аргументов, тела и среды (и таких атрибутов, как класс), и перемещение функции в другое место не меняет ни одну из этих составляющих.

Исправление

Нам необходимо явно установите для окружения testfun значение e10, если мы хотим, чтобы оно имело это в качестве окружения и чтобы e4 было предком:

environment(e10$testfun) <- e10

или, альтернативно, мы не могли определить testfun в глобальном окружении, в первую очередь, а точнее определить testfun в окружении e10 с самого начала:

with(e10, {
  testfun <- function() testvar
})

e10$testfun()
## [1] 1200

Имена функций

Может быть еще одна путаница заблуждение, что следующий оператор определяет функцию с именем testfun в e10.

e10$testfun <- function() testvar

Проблема этой идеи заключается в том, что функции не имеют имен. Три составляющих функции - это аргументы, тело и окружение (и такие атрибуты, как class и, возможно, srcref и scrfile). Имя не является составной частью функции. Можно поместить функцию в переменную и ссылаться на эту переменную, как если бы это было имя функции, но на самом деле это просто переменная, которая содержит функцию, а имя не является частью самой функции. Таким образом, в приведенной выше строке кода мы не определяем функцию с именем testfun; скорее, мы определяем анонимную функцию (в глобальной среде) и затем перемещаем ее в переменную testfun.

Пример из самого R *

Хотя она является общей для пользовательских функций оставаться в среде, в которой они определены, например, функции

f <- function() "hello"

# the environment of f
environment(f)
## <environment: R_GlobalEnv>

# the environment where f is located (same)
as.environment(find("f"))
## <environment: R_GlobalEnv>

в R-пакетах на пути поиска

# show search path
search()

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

# the environment of function mean
e1 <- environment(mean); e1
## <environment: namespace:base>

# where mean is located
e2 <- as.environment(find("mean")); e2
## <environment: base>

# these are NOT the same
identical(e1, e2)
## [1] FALSE

Это хорошо показано на диаграммах в этом посте: http://blog.obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/

proto

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

Сначала мы определяем объект прото p, родительским объектом которого является e9, а затем присваиваем интересующую функцию p. Наконец мы запускаем эту функцию. (Первый аргумент неявно является объектом-прототипом, поэтому мы его опускаем.) Мы видим, что функция действительно сбросила свое окружение и что e4 теперь является предком своего окружения, не устанавливая его явно.

library(proto)

p <- proto(e9)  # define proto object whose parent is e9
p$testfun <- function(self) testvar

identical(p, with(p, environment(testfun)))  # testfun's environment is now p
## [1] TRUE

p$testfun()
## [1] 1200
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...