2017-06-29 60 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