2017-06-29 61 views
8

在下面的例子中,为什么f$if$get_i()返回不同的结果?访问变量R中的闭包

factory <- function() { 

    my_list <- list() 
    my_list$i <- 1 

    my_list$increment <- function() { 
    my_list$i <<- my_list$i + 1 
    } 

    my_list$get_i <- function() { 
    my_list$i 
    } 

    my_list 
} 

f <- factory() 

f$increment() 
f$get_i() # returns 2 
f$i # returns 1 
+4

是不是你只是改变'my_list $ i'的值而不是'f $ i'的值?这些不是同一个变量。如果'my_list'存在于全局环境中,它的'i'参数会被改变,但是你不会改变'f'对象的任何项目 – Cath

+0

谢谢 - 是的,它们不是同一个变量,这是令人惊讶的。 我猜我试图理解_why_他们不是同一个变量。我们在R中看到的行为与JavaScript中发生的行为不同。 – RobinL

+0

对不起,这是微不足道的答案,但我仍然认为这是它的原因:它只是不同的名称(尽管两个“代表”相同的变量) – Cath

回答

4

你的编码方式与功能范例非常相似。 R更经常用作脚本语言。所以,除非你确切地知道你在做什么,这是不好的做法是使用< < - 或包括在函数的函数。

您可以在功能环境章节找到解释here

环境是一个空间/帧在那里执行代码。环境可以嵌套,就像功能一样。

创建功能时,您有附加的机柜环境,可由environment调用。这是封闭的环境。

该函数在另一个环境中执行,执行环境采用全新的启动原则。执行环境是封闭环境的子环境。

对于为例,我的笔记本电脑:

> environment() 
<environment: R_GlobalEnv> 
> environment(f$increment) 
<environment: 0x0000000022365d58> 
> environment(f$get_i) 
<environment: 0x0000000022365d58> 

f是位于全球环境的对象。

功能increment贴有封闭环境0x0000000022365d58,函数factory的执行环境。

我从哈德利引用:

当另一个函数内部创建一个函数,封闭 环境中的孩子的功能是 父的执行环境,执行环境不再是短暂的。

执行函数f时,将使用其中的my_list对象创建封闭环境。

可与ls命令进行评估:

> ls(envir = environment(f$increment)) 
[1] "my_list" 
> ls(envir = environment(f$get_i)) 
[1] "my_list" 

<<-运营商在父母搜索环境中使用的变量。在这种情况下,找到的my_list对象是紧邻上层环境中的对象,它是该函数的封闭环境。

因此,当增量作出时,它只在该环境中而不在全局中。

您可以通过在更换increment功能看它:

my_list$increment <- function() { 
    print("environment") 
    print(environment()) 
    print("Parent environment") 
    print(parent.env(environment())) 
    my_list$i <<- my_list$i + 1 
    } 

它给我:

> f$increment() 
[1] "environment" 
<environment: 0x0000000013c18538> 
[1] "Parent environment" 
<environment: 0x0000000022365d58> 

您可以使用get来访问你的结果,一旦你已经存储环境名称:

> my_main_env <- environment(f$increment) 
> get("my_list", env = my_main_env) 
$i 
[1] 2 

$increment 
function() 
{ 
    print("environment") 
    print(environment()) 
    print("Parent environment") 
    print(parent.env(environment())) 
    my_list$i <<- my_list$i + 1 
} 
<environment: 0x0000000022365d58> 

$get_i 
function() 
{ 
    print("environment") 
    print(environment()) 
    print("Parent environment") 
    print(parent.env(environment())) 
    my_list$i 
} 
<environment: 0x0000000022365d58> 
+0

谢谢。最后一点是这个解释:'当'my_list'从'factory'返回并分配给'f'时,它在全局环境中作为一个列表存在,并且不再能够访问'factory'的环境关闭)。由于没有链接,因此对'工厂'环境的更改不会影响'f'。我们仍然可以通过在'my_list $ increment'函数中使用'<< - '来更改'factory'环境中的'my_list',但访问这个环境的唯一方法是通过一个显式的getter函数'my_list $ get_i' – RobinL

+0

这是完全正确的。 – YCR

6
f <- factory() 

my_list创建的对象与my_list$i = 1并将其分配给f。所以现在f$i = 1

f$increment() 

只会增加my_list$i而已。它不会影响f

现在

f$get_i() 

回报(以前递增)my_list$i

收益不受影响f$i

这是因为你使用的是在全局对象工作<<-运营商。如果你改变你的代码

my_list$increment <- function(inverse) { 
    my_list$i <- my_list$i + 1 
} 

my_list只会里面increment功能递增。所以,现在你

> f$get_i() 
[1] 1 
> f$i 
[1] 1 

让我多了一个行添加到您的代码,所以我们可以看到increment的肠子:

my_list$increment <- function(inverse) { 
    my_list$i <- my_list$i + 1 
    return(my_list$i) 
    } 

现在,你可以看到<-工作只有内部increment<<-其外部操作。

> f <- factory() 
> f$increment() 
[1] 2 
> f$get_i() 
[1] 1 
> f$i 
[1] 1 
+0

谢谢。我明白'f $ increment()影响'my_list'而不是'f',但我不知道为什么。闭包中的my_list和返回的my_list是不同的,然后分配给f。这是因为R总是按值使用引用,所以'factory'函数返回的内容不是对闭包中的'my_list'的引用,而是'my_list'的副本? – RobinL

+0

查看更新回答:) –

+0

下一个问题可能是 - 如何在'f'里面有一个真正的“全局”'my_list $ i'。 –

4

根据@Cath对“value by reference”的评论,我很受启发想出了这个。

library(data.table) 
factory <- function() { 
    my_list <- list() 
    my_list$i <- data.table(1) 

    my_list$increment <- function(inverse) { 
    my_list$i[ j = V1:=V1+1] 
    } 

    my_list$get_i <- function() { 
    my_list$i 
    } 
    my_list 
} 
f <- factory() 
f$increment() 
f$get_i() # returns 2 
    V1 
1: 2 
f$i # returns 1 
    V1 
1: 2 
f$increment() 
f$get_i() # returns 2 
    V1 
1: 3 
f$i # returns 1 
    V1 
1: 3