Управление окружающей средой функции

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

Основываясь на разделе Description страницы справки на rlang::fn_env(), я понял, что функция всегда имеет доступ ко всем переменным в своей области и что окружающая ее среда принадлежит этой области.

Но тогда почему нельзя манипулировать содержимым среды замыкания «постфактум», т.е. после создания функции?

С помощью лексической области видимости R не должен ли bar() найти x, когда я помещаю его в окружающую среду?

foo <- function(fun) {
  env_closure <- rlang::fn_env(fun)
  env_closure$x <- 5
  fun()
}

bar <- function(x) x

foo(bar)
#> Error in fun(): argument "x" is missing, with no default

person Rappster    schedule 15.01.2019    source источник


Ответы (2)


Ах, я думаю, что я понял это сейчас.

Это связано со структурой формальных аргументов функции:

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

Один из способов запустить лексическую область видимости, даже если вы не хотите определять значение по умолчанию, — это установить значения по умолчанию «на лету» во время выполнения через rlang::fn_fmls().

foo <- function(fun) {
  env_enclosing <- rlang::fn_env(fun)
  env_enclosing$x <- 5
  fun()
}

# No argument at all -> lexical scoping takes over
baz <- function() x
foo(baz)
#> [1] 5

# Set defaults to desired values on the fly at run time of `foo()`
foo <- function(fun) {
  env_enclosing <- rlang::fn_env(fun)
  env_enclosing$x <- 5
  fmls <- rlang::fn_fmls(fun)
  fmls$x <- substitute(get("x", envir = env_enclosing, inherits = FALSE))
  rlang::fn_fmls(fun) <- fmls
  fun()
}

bar <- function(x) x
foo(bar)
#> [1] 5
person Rappster    schedule 15.01.2019

Я не могу следовать вашему примеру, так как я не знаком с библиотекой rlang, но я думаю, что хорошим примером замыкания в R будет:

bucket <- function() {
    n <- 1
    foo <- function(x) {
        assign("n", n+1, envir = parent.env(environment()))
        n
    }
    foo
}
bar <- bucket()

Поскольку bar() определяется в функциональной среде bucket, его родительской средой является bucket, и поэтому вы можете переносить туда некоторые данные. Каждый раз, когда вы запускаете его, вы изменяете среду bucket:

bar()
[1] 2
bar()
[1] 3
bar()
[1] 4
person trosendal    schedule 15.01.2019
comment
Спасибо за ответ. Я знаком с функцией, созданной внутри функционального подхода, но специально пытался лучше понять окружающие среды для функции, созданной внутри нефункциональной среды (например, пространство имен пакета или .GlobalEnv - person Rappster; 15.01.2019
comment
Можете ли вы помочь мне понять, почему у вас есть доступ к x в первом примере? Похоже, что baz() должна иметь родительскую среду .GlobalEnv и, следовательно, искать внутри себя x, а затем до .GlobalEnv, когда она оценивается внутри foo() - person trosendal; 15.01.2019
comment
Хорошо, я понял: запуск rlang::fn_env() изменяет среду функции, так что ее новый родитель становится foo()... классным. Таким образом, вы фактически строите замыкание так же, как в моем примере, но определяете baz() отдельно, а затем изменяете его. - person trosendal; 15.01.2019
comment
Точно :-) Не знаю, понадобится ли мне это когда-нибудь, но вычисления на самом языке — это очень весело. Вы обязательно должны проверить пакет rlang, еще один шедевр от RStudio и друзей :-) - person Rappster; 15.01.2019