R 的 Environment

R 的 Environment 的重要性,有兩面

1. 對 R 語言本身
2. 對資料結構

對 R 語言本身的重要性在於 Environment 是實現 R 的 functional language 中的 lexical scoping 的基礎 ; 對資料結構而言,在 Environment 的 reference 機制威力,不像大部分的 R 物件,當你修改一個 environment,它不會再產生一個新的 environment copy  也就是說,用它可作為複雜資料結構的基礎。[這部份本篇不會再多說了。因為你可能不會用到。]

Environment 可集合不同物件,R 中的 list 也有相同的能力。 Environment 與 list 相較有下列四點的不同:

1. 在 environment 中的每個物件都需要有名字
2. 在 environment 中物件的順序不重要
3. 除了 empty environment 外,Environment 有都有對應一個 parent
4.在 Environment 中,以名字 reference 的方式來參照到所有在其中的物件

所以,Environment 是物件名字的集合。在 environment 中以名字來引用物件,所以沒有所謂物件的順序。每一個 environment 都有一個 parent environment,它是用來實作 lexical scoping 的: 也就是當一個名稱沒有在目前的 environment 找到,則 R 就會去它的 parent environment 找,以此類推直到最後的 environment(一個 empty environment)。

對 R 執行環境,一個名字的查找 environment 路徑(尋根之旅:)如下 :

如果在 R 的 workspace 內執行一名稱的查找,則 R 會先在 globalenv environment 內查找;如果沒發現,R 會在你最後用 library 引入的 package environment 內查找;接著按你用 library 引入 package 的順序反向查找;package environment 找完後,最後  baseenv: base package 的 environment。而 baseenv 的 parent 的 environment 為 emptyenv environment。emptyenv 如前所述就沒有 parent environment。

另一個 environment 在 R 中扮演的重要角色是函式的 environment。 跟函式有關的 environment 有四種類型:

1. enclosing
2. binding
3. execution
4. calling

enclosing environment: 所謂 enclosing environment 就是函式定義所在的 environment。每一個函式只有對應到一個且唯一的 enclosing environment。 它用來決定 lexical scoping,如何來查找物件的值。一函式可能對應到 0、1、或多個其它類型的 environments。

binding environment: 所謂 binding environment 就是以 <-  定義將一個函式內容綁定(bind)一個名字的 environment。它用來決定如何找到一個函式。
<- 在目前 environment 產生一個綁定
<<- 在目前 environment 的 parent 重新綁定一個已經存在的名字。


execution environment: 所謂 execution environment 就是在呼叫函式時會產生一個新的暫時用來儲存函式執行時所需要的變數的 environment。而這個暫存 environment 的 parent environment 就是此函式的 enclosing environment。一旦函式執行完畢此 execution environment 將會被 gc 回收。但是,當你在一個函式內去定義一個函式時,則此子函式的 enclosing environment 就是此 execution environment,此時它就不在是暫時存在的 environment。 例如

> pf <- function(x) {
    function(y) x * y
 }

> p2 <- pf(2)


> identical(parent.env(environment(p2)), environment(f))
   
   TRUE

另外,請注意每一個 execution environment 有兩個 parent: 一個是 enclosing environment; 另一個是 calling environment。 R 在經常性的 scoping 時使用 enclosing environment;但在 parent.frame 讓你能取得 calling environment。

calling environment: 所謂 calling environment  就是呼叫函式時所在的 environment。所以,它會伴隨 execution environment。calling environment 用在 dynamic scoping。所謂 dynamic scoping 簡單說就是找變數不在 enclosing 而是往 calling environment 找。例如,

>  p1 <- function() {
+         x <- 2
+        function() {
+            rs <- get("x", environment())
+            ds <- get("x", parent.frame())
+            list(regular = rs, dynamic = fd)
+   }
+ }
> p12 <- p1()
> x <- 20
> str(p12())
List of 2
 $ regular: num 2

 $ dynamic : num 20


Enclosing 與 binding environment 的不同對 package 的 namespace 而言是重要的。Package namespace 保持 package 的獨立性。例如: 假如 package A 用到了 base 的 mean 函式,這時假如 package B 定義了屬於它自己的 mean 函式會怎樣? Namespace 機制會確保 package A 繼續使用到 base 的 mean 函式,且 package A 不受 package B 影響。

Namespace 實作就是用到 environment 的好處,函是不必一定要待在它的 enclosing environment。例如:

> environment(sd)
<environment: namespace:stats>
> where("sd")
<environment: package:stats>

由上可知 package stats 有兩個 environments: namespace 與 package 。namespace environment 是 在 stat package 內所定義函式例如 sd 的 enclosing environment;而 package environment 是在 stat package 內所定義函式例如 sd 的 binding environment。所以,

> x <- 1:10
> sd(x)
[1] 3.02765

sd 函是會呼叫, var 函式。 雖然我們自己定義了 var ,也不會影響到 sd 的執行

> var <- function(x, na.rm = TRUE) 100
> sd(x)
[1] 3.02765

這一切是怎麼運作的? 這就是利用 namespace 與 package environment 的協作了。package environment 包含了此 state package 的所有公開函式,而且將此 environment 放在查找路徑上。所以,在 R 可以在 stat 的 package environment 找到 sd 函式;而 namespace environment 則包含了所有 stat package 的函式(包含內部函式) 且它的 parent environment 是一包含所有此 stat package 需要的所有外部函式(例如 var)綁定(例如綁定到 base)匯入的 environment。所以,如下圖在執行 sd 時,遇到 var,R 會往 sd 函式的 enclosing 也就是 imports environment  找到綁定到正確 base package 的 var 函式: 如下圖所示:  




本篇主要內容來至 Advanced R。但我覺得 environment 重要,所以我將它的內容重新整理一番。希望有益理解。












留言

這個網誌中的熱門文章

標準差與 Wald 統計量

可能性比檢定(Likelihood ratio test)

Wold Decomposition Theorem