【问题标题】:Manipulating enclosing environment of a function操作函数的封闭环境
【发布时间】:2019-06-09 07:44:42
【问题描述】:

我试图更好地理解closures,特别是关于函数的作用域以及如何使用它的封闭环境的细节

根据rlang::fn_env() 帮助页面的Description 部分,我了解到,函数始终可以访问其范围内的所有变量,并且其封闭环境属于该范围。

但是,为什么不能在“事后”(即在函数创建之后)操作闭包环境的内容?

通过 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

【问题讨论】:

    标签: r scope closures rlang


    【解决方案1】:

    啊,我想我现在明白了。

    这与函数形式参数的结构有关:

    如果定义的参数没有默认值,当你调用函数时 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
    

    【讨论】:

      【解决方案2】:

      由于我不熟悉 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
      

      【讨论】:

      • 感谢您的回答。我熟悉“在函数内部创建的函数”方法,但专门试图更好地理解“函数是在非功能环境中创建的”的封闭环境(例如包的命名空间或.GlobalEnv
      • 你能帮我理解为什么在你的第一个例子中你可以访问x吗?看起来baz() 应该有父环境.GlobalEnv,因此在x 内部查找.GlobalEnv,然后在foo() 内部评估时查找.GlobalEnv
      • 好的,我明白了:运行 rlang::fn_env() 会修改函数的环境,因此它的新父级变为 foo()... 很酷。所以你实际上是在构建一个与我的示例相同的闭包,但分别定义 baz() 然后对其进行修改。
      • 完全正确 :-) 不知道我是否会需要它,但是在语言本身上进行计算非常有趣。您绝对应该查看rlang 包,这是 RStudio 和朋友们的又一杰作 :-)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-04
      • 2012-05-25
      • 2016-01-27
      • 1970-01-01
      • 2012-04-01
      相关资源
      最近更新 更多