【问题标题】:Distinct enclosing environment, function environment, etc. in RR中不同的封闭环境、函数环境等
【发布时间】:2017-06-08 06:01:37
【问题描述】:

我有几个关于函数的不同环境的问题。举个例子:

environment(sd)
# <environment: namespace:stats>

namespace:stats 是否指向函数 sd 的封闭环境?

pryr::where(sd) 
# <environment: package:stats>

package:stats 是否指向函数 sd 的绑定环境?

根据Advanced R by Hadley Wickham:“封闭环境属于函数,永不改变……”

但是函数的封闭环境可以改变如下:

new.env <- new.env()
environment(f) <- new.env

函数的环境属性表示函数的执行环境,对吗? An online article regarding R finding stuff through environments

总结一下我的问题:

  1. 我们能否真正改变函数的封闭环境?
  2. stats 包的这两种不同环境是什么?
  3. 函数的环境是什么?

这与之前的帖子in here 类似。

【问题讨论】:

  • 不确定那里有问题,但这里有几个相关的 cmets: 1. 当在创建的环境中找不到变量时,函数的环境是继续搜索变量的地方当函数被调用时。 2. 用户(以及包)可以改变很多 R 自己不会改变的东西。我从 R 的内部结构来解释 Hadley 的 cmets。
  • @Jthorpe 我的帖子中提到了几个问题。我不明白为什么“封闭环境属于函数,永远不会改变......”是真的还是假的?此外,我已经知道您的评论 1,但我的困惑是作者说 environment(function) 表示函数的执行环境,我不明白。
  • @Amy 我冒昧地编辑了您的问题,使其更加流畅。我还编辑了我的答案。我错了,您无需复制即可更改该环境。只有当它是一个打包的函数时,你才能这样做。

标签: r function environment


【解决方案1】:

TLDR:

  1. 确实,您可以更改封闭环境。 Hadley 可能在谈论打包函数。
  2. 封闭和绑定环境。你是对的。
  3. 这就是执行环境。它仅在函数运行时存在。

函数环境

在谈论函数时,您必须区分 4 种不同的环境:

  • 绑定环境是函数所在的环境(即其名称所在的位置)。这是对象与其名称的实际绑定完成的地方。 find() 为您提供绑定环境。
  • 封闭环境是最初创建函数的环境。这不一定与绑定环境相同(请参见下面的示例)。 environment() 为您提供封闭环境。
  • 本地环境是函数内的环境。你称之为执行环境。
  • 父框架或调用环境是调用函数的环境。

为什么这很重要

每个环境都有特定的功能:

  • 绑定环境是您找到函数的环境。
  • 本地环境是 R 查找对象的第一个环境。
  • 一般规则是:如果 R 在本地环境中没有找到对象,则在封闭环境中查找,依此类推。最后一个封闭环境始终是emptyenv()
  • 父框架是 R 查找传递的对象值的地方 论据。

你可以改变封闭环境

确实,您可以更改封闭环境。它是您无法更改的包中函数的封闭环境。在这种情况下,您无需更改封闭环境,而是实际上在新环境中创建了一个副本:

> ls()
character(0)
> environment(sd)
<environment: namespace:stats>
> environment(sd) <- globalenv()
> environment(sd)
<environment: R_GlobalEnv>
> ls()
[1] "sd"
> find("sd")
[1] ".GlobalEnv"    "package:stats" # two functions sd now
> rm(sd)
> environment(sd)
<environment: namespace:stats>

在这种情况下,第二个sd具有全局环境作为封装和绑定环境,但原来的sd仍然存在于包环境中,其封装环境仍然是该包的命名空间

当您执行以下操作时,可能会出现混淆:

> f <- sd
> environment(f)
<environment: namespace:stats>
> find("f")
[1] ".GlobalEnv"

这里发生了什么?封闭环境仍然是命名空间“stats”。这就是创建函数的地方。但是,绑定环境现在是全局环境。这就是名称“f”与对象绑定的地方。

我们可以将封闭环境更改为新环境e。如果现在检查,封闭环境变为e,但e 本身为空。 f 仍然绑定在全局环境中。

> e <- new.env()
> e
<environment: 0x000000001852e0a8>
> environment(f) <- e
> find("f")
[1] ".GlobalEnv"
> environment(f)
<environment: 0x000000001852e0a8>
> ls(e)
character(0)

e 的封闭环境是全局环境。所以f 仍然像它的外壳是全球环境一样工作。环境e 包含在其中,因此如果在e 中找不到某些内容,则该函数会在全局环境中查找,依此类推。

但是因为e 是一个环境,R 将其称为父环境。

> parent.env(e)
<environment: R_GlobalEnv>
> f(1:3)
[1] 1 

命名空间和包环境

这个原理也是包使用的“绝招”:

  • 函数在命名空间中创建。这是一个被其他导入包的名称空间包围的环境,最终是全局环境。
  • 函数的绑定是在包环境中创建的。这是一个包含全局环境和可能的其他包的环境。

原因很简单:对象只能在您所在的环境中或其封闭环境中找到。

  • 函数必须能够找到其他函数(对象),因此本地环境必须包含在它导入的其他包的命名空间中,包括基础包,最后是全局环境。
  • 必须可以从全局环境中找到函数。因此,绑定(即函数的名称)必须位于由全局环境包围的环境中。这是包环境(不是命名空间!)

插图:

现在假设您创建了一个以空环境为父环境的环境。如果将其用作函数的封闭环境,则不再起作用。因为现在你绕过了所有的包环境,所以你再也找不到一个函数了。

> orphan <- new.env(parent = emptyenv())
> environment(f) <- orphan
> f(1:3)
Error in sqrt(var(if (is.vector(x) || is.factor(x)) x else as.double(x),  : 
  could not find function "sqrt"

父框架

这就是有趣的地方。父框架或调用环境是查找作为参数传递的值的环境。但是那个父框架可以是另一个函数的本地环境。在这种情况下,R 首先在该其他函数的本地环境中查找,然后在调用函数的 封闭 环境中查找,一直到全局环境,即附加包的环境直到它到达空的环境。这就是“找不到对象”错误的所在。

【讨论】:

  • @jenesaisquoi 没有。 stats 的命名空间是锁定的,所以你不能在其中分配任何东西。而如果你在全局环境中创建f(例如f &lt;- function(){NULL}),那么你改变了全局环境的内容,但是f的封闭环境还是一样的。您不能更改函数的封闭环境。
  • @jorisMeys 非常精彩和详细的插图,这是我真正需要更好、更全面地了解 R 中的函数环境。
  • @jenesaisquoi 根据 Joris 对我的问题的回答,“您不会更改封闭环境,实际上是在新环境中创建副本”。说明原来的函数(f)的封闭环境还是和f一样。
  • 我个人认为,增加构建 R 包的经验肯定会加深我们对所有这些的理解。
  • @jenesaisquoi 您的代码没有更改f 的封闭环境,但您实际上可以更改它。因此我错了。 R 也有与环境相关的可怕术语。封闭环境,父环境,令人困惑。我已经相应地编辑了我的答案。感谢您的帮助。
【解决方案2】:

environment(function) 给出了函数的封闭环境(即闭包),它被分配了一个指向定义函数的环境的指针。这种约定称为词法作用域,它允许您使用像工厂函数这样的模式。这是一个简单的例子

factory <- function(){
    # get a reference to the current environment -- i.e. the environment 
    # that was created when the function `factory` was called.
    envir = environment()
    data <- 0
    add <- function(x=1){
        # we can use the lexical scoping assignment operator to re-assign the value of data
        data <<- data + x
        # return the value of the lexically scoped variable `data`
        return(data)
    }
    return(list(envir=envir,add=add))
}

L = factory()

# check that the environment for L$add is the environment in which it was created
identical(L$envir,environment(L$add))
#> TRUE

L$add()
#> 1
L$add(3)
#> 4

请注意,我们可以使用 assign() 在封闭环境中重新分配 data 的值,如下所示:

assign("data",100,L$envir)
L$add()
#> 101

另外,当我们再次调用函数factory()时,会创建另一个新环境 并被分配为在其中定义的函数的闭包 函数调用,这使我们不得不分离 foo$add() 函数 范围到他们自己独立的环境:

M = factory()
M$add()
#> 1
#> 2
L$add()
#> 102

上面的工厂函数通过继续搜索变量(以及使用范围赋值运算符)说明了函数和它的封闭环境之间的联系,而下面通过 Promises 说明了本地环境和调用框架之间的联系这就是 R 在函数调用中传递变量的方式。

具体来说,当你调用一个函数时,R 会为传递的变量和表达式的值创建 Promise。当参数为 force()'d 或已使用时,通过在调用环境的上下文中评估 Promise 来从变量/表达式传递(复制)这些 Promise 值——而不是更早!

例如,这个工厂函数接受一个参数,该参数存储为一个承诺,直到返回的函数被调用:

factory2 <- function(x){
    out <-function(){
         return(x)
    }
    return(out)
}

现在factory2 在某些情况下表现得非常直观:

y = 1
f = factory2(y)
f()
#> 1

但不是在其他人中:

y = 1
h = factory2(y)
y = 2
h()
#> 2

因为表达式y 的承诺在h() 被调用之前不会被计算,而在第二个例子中,y 的值是2!当然,既然已经通过 Promise 评估将值从调用环境复制到本地环境中,更改 y 的值不会影响 h() 返回的值:

y = 3
h()
#> 2

【讨论】:

  • 工厂函数的好例子,但你并没有真正回答这个问题......
  • @JorisMeys 我同意。无论如何,对于我的 R 研究来说,这是一个有用的有见地的例子。
  • 只要你在学习 R 环境,你还应该看看 Promises,它是父环境和本地环境之间的链接(参见修订后的答案。
  • @Jthorpe 也很好地说明了承诺。我会说虽然它是调用和本地环境之间的链接。我不喜欢使用“父母”这个词,因为 R 对此有点模棱两可。在谈论环境时,父级是封闭环境,而在函数的上下文中,父级是调用环境。所以你说的并没有错,但这是整个环境故事混乱的根源。
猜你喜欢
  • 2019-06-09
  • 2021-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多