2012-06-20 5 views
3

В моей разработке R мне нужно обернуть функциональные примитивы в объектах proto, чтобы несколько аргументов могли автоматически передаваться функциям при вызове метода объекта $perform(). Внутренний вызов функции происходит через do.call(). Все хорошо, за исключением случаев, когда функция пытается получить доступ к переменным из замыкания, в котором оно определено. В этом случае функция не может разрешить имена.Смещение окружения в R

Вот маленький пример, который я обнаружил, что воспроизводит поведение:

library(proto) 

make_command <- function(operation) { 
    proto(
    func = operation, 
    perform = function(., ...) { 
     func <- with(., func) # unbinds proto method 
     do.call(func, list(), envir=environment(operation)) 
    } 
    ) 
} 

test_case <- function() { 
    result <- 100 
    make_command(function() result)$perform() 
} 

# Will generate error: 
# Error in function() : object 'result' not found 
test_case() 

У меня есть воспроизводимый testthat тест, который также выводит много диагностической продукции. Диагностический вывод меня озадачил. Изучая цепочку родительских окружений, мой диагностический код, который живет внутри функции, находит и печатает ту же самую переменную, которую функция не находит. See this gist..

Как можно настроить среду для do.call?

ответ

3

Это был окончательный ответ после обсуждения в автономном режиме с плакатом:

make_command <- function(operation) { 
proto(perform = function(.) operation()) 
} 
+0

Спасибо за ваши последующие действия по этому вопросу. Не могли бы вы помочь мне понять, почему это работает? Это происходит так: ** (1) ** '.' В вызове 'proto()' относится к оценочному фрейму/среде вызова 'make_command()', который таким образом становится родительским (т.е. '.super') нового прото-объекта; так что ** (2) **, когда «операция» не найдена в прото-объекте, ее ищут и обнаруживают в ее '.super'; а затем ** (3) ** 'operation' сам имеет в качестве среды оценочный фрейм/среду вызова' test_case() '(где« результат »также живет)? –

+0

Прото-объект - это окружающая среда. Протольный объект здесь имеет один компонент, 'perform', который является функцией/методом. среда 'perform' устанавливается на объект proto, когда она вставлена, поэтому, когда' execute' запускает ее, она сначала ищет 'операцию' в прото-объекте и когда ее не обнаружено там, она выглядит в родительском элементе proto. Родитель прото-объекта не был указан, поэтому он по умолчанию задает среду, в которой был определен объект 'proto', который является средой в кадре выполнения' make_command', и там он находит «операцию» в качестве аргумента. См. Новые часто задаваемые вопросы на домашней странице прото. –

+0

Спасибо за дополнительные вопросы/комментарии и благодарности за новые часто задаваемые вопросы. – Sim

1

Я думаю, что проблема здесь яснее и проще исследовать, если вы:

  • Заменить анонимную функцию в make_command() с именем одного.

  • Сделайте эту функцию открытой browser() (вместо того, чтобы пытаться получить result). Таким образом, вы можете посмотреть вокруг, где вы находитесь и что происходит.

Попробуйте это, что следует уточнить причину вашей проблемы:

test_case <- function() { 
    result <- 100 
    myFun <- function() browser() 
    make_command(myFun)$perform() 
} 
test_case() 
## Then from within the browser: 
# 
parent.env(environment()) 
# <environment: 0x0d8de854> 
# attr(,"class") 
# [1] "proto"  "environment" 
get("result", parent.env(environment())) 
# Error in get("result", parent.env(environment())) : 
# object 'result' not found 
# 
parent.frame() 
# <environment: 0x0d8ddfc0> 
get("result", parent.frame()) ## (This works, so points towards a solution.) 
# [1] 100 

Вот проблема. Хотя вы считаете, что оцениваете myFun(), чья среда представляет собой оценочный фрейм test_case(), ваш звонок в do.call(func, ...) действительно оценивает func(), окружением которого является среда proto, в которой она была определена. После поиска и отсутствия result в своем собственном фрейме, вызов func() следует правилам лексического охвата, а затем выглядит в среде proto. Ни он, ни его родительская среда содержит объект с именем result, в результате чего вы получили сообщение об ошибке.

Если это не имеет смысла, вы можете продолжать искать в браузере. Вот несколько дополнительных вызовов может оказаться полезной:

environment(get("myFun", parent.frame())) 
ls(environment(get("myFun", parent.frame()))) 
environment(get("func", parent.env(environment()))) 
ls(environment(get("func", parent.env(environment())))) 
+0

Спасибо, эти полезные советы отладки. Какое чтение вы бы порекомендовали для действительно понимания цепочки областей в R? – Sim

+0

Одна небольшая точка разъяснения о вашем ответе: среда func - это прото-объект, но не потому, что это то, где оно определено. Proto обертывает функции в классе, чтобы превратить их в созданные методы. с (., func) разворачивает их и _should_ работал, за исключением того, что реализация proto фактически сбрасывает среду функции, чтобы ее код мог получить доступ к внутренностям прото-объекта.Это то, что перезагрузка происходит за кулисами, что является причиной поведения (и путаницы). – Sim

+0

@Sim - Спасибо за разъяснение. В разделе 13.3 «Программное обеспечение для анализа данных» Джона Чамберса дается четкое объяснение сферы охвата в Р. Одна путающая вещь, которую он делает, последовательно конкретизирует окружающие среды «родительские среды». Я считаю, что это несчастливо, так как оно мутирует важное различие между стеком вызовов (цепочкой родительских кадров) и цепочкой окружающих сред, по которым оценщик выполняет поиск значений символов, не присутствующих в локальной среде. –