環境 (その1)

Rの環境について,2回に分けてまとめます.


第1回目は,環境全般とスコープ規則について.
第2回目は,環境を操作する関数についての予定.


なお,『Rプログラミングマニュアル』(間瀬茂, 2007年, 数理工学社) を参考にしました.
言語としてのRの基礎や,豊富な役立つtipsが載っており,とても参考になる本です.


環境

環境は,frame (オブジェクトのシンボル名と対になる値の集合) と,その囲み環境 (enclosing environment) へのポインタであるenclosureからなる.
囲み環境には,関数 parent.env() でアクセスできる.
環境はツリー構造をしている.ルートノードは emptyenv() という空の環境で,R base package の囲み環境になっている.

> globalenv()
<environment: R_GlobalEnv>
> parent.env(.GlobalEnv)
<environment: 0x1f88678>
attr(,"name")
[1] "tools:RGUI"
> baseenv()
<environment: base>
> parent.env(baseenv())
<environment: R_EmptyEnv>


関数 new.env() は,指定した環境を囲み環境とする新たな空の環境をつくる.

> (env <- new.env(parent = .GlobalEnv))
<environment: 0x1601c9f8>
> parent.env(env)
<environment: R_GlobalEnv>


環境中のオブジェクトには,関数 ls(), get(), assign(), eval() などでアクセスできる.

> ls(.GlobalEnv)
[1] "env"    "R_LIBS"
> head(ls(baseenv()))
[1] "^"        "-"        "-.Date"   "-.POSIXt" ":"        "::"      


環境は他のオブジェクトと異なり,参照渡しで,コピーされない.よって,複数の変数が同一環境を指していた場合,一方を変更すると他方も変更される.

> # Environment.
> env1 <- new.env()
> env2 <- env1
> assign("x", rnorm(3), envir = env1)
> ls(envir = env2)
[1] "x"
> get("x", envir = env1)
[1] -0.29572096 -0.06950039  0.80579259
> get("x", envir = env2)
[1] -0.29572096 -0.06950039  0.80579259
> # Object.
> obj1 <- numeric(3)
> obj2 <- obj1
> assign("obj1", rnorm(3))
> get("obj1", envir = .GlobalEnv)
[1] -0.8161385 -0.5597408 -0.6494503
> get("obj2", envir = .GlobalEnv)
[1] 0 0 0


サーチパス

現在のパスは関数 search() で得られる.

> search()
 [1] ".GlobalEnv"        "tools:RGUI"        "package:stats"    
 [4] "package:graphics"  "package:grDevices" "package:utils"    
 [7] "package:datasets"  "MacJapanEnv"       "package:methods"  
[10] "Autoloads"         "package:base"     


ある環境中のオブジェクト一覧は関数 ls() で見られる.

> head(ls(pos = "package:stats"))
[1] "acf"        "acf2AR"     "add.scope"  "add1"       "addmargins"
[6] "aggregate" 
> head(ls(pos = 3))
[1] "acf"        "acf2AR"     "add.scope"  "add1"       "addmargins"
[6] "aggregate" 


attach() で登録されたオブジェクトや読み込まれたパッケージは,.GlobalEnv以下に,読み込まれた順に付加されていく.

> library(glmmML)
> x <- data.frame(x = rnorm(3))
> attach(x)
The following object(s) are masked _by_ '.GlobalEnv':

    x
> search()
 [1] ".GlobalEnv"        "x"                 "package:glmmML"   
 [4] "tools:RGUI"        "package:stats"     "package:graphics" 
 [7] "package:grDevices" "package:utils"     "package:datasets" 
[10] "MacJapanEnv"       "package:methods"   "Autoloads"        
[13] "package:base"     
> parent.env(as.environment("package:glmmML"))
<environment: 0x1972078>
attr(,"name")
[1] "tools:RGUI"
> parent.env(as.environment(2))
<environment: package:glmmML>
attr(,"name")
[1] "package:glmmML"
attr(,"path")
[1] "/Library/Frameworks/R.framework/Versions/2.14/Resources/library/glmmML"


名前空間

他の環境からのマスクに影響されることなく,パッケージ内でオブジェクトの参照を解決する仕組み.

> IQR # package:stats の中にあり,公開されている.
function (x, na.rm = FALSE, type = 7) 
diff(quantile(as.numeric(x), c(0.25, 0.75), na.rm = na.rm, names = FALSE, 
    type = type))
<bytecode: 0x1e7aa78>
<environment: namespace:stats>
> predict.nls # package:stats の中にあるが,公開されていない.
 エラー:  オブジェクト 'predict.nls' がありません 
> getAnywhere(predict.nls)
A single object matching ‘predict.nls’ was found
It was found in the following places
  registered S3 method for predict from namespace stats
  namespace:stats
with value

function (object, newdata, se.fit = FALSE, scale = NULL, df = Inf, 
    interval = c("none", "confidence", "prediction"), level = 0.95, 
    ...) 
{
    if (missing(newdata)) 
        return(as.vector(fitted(object)))
    if (!is.null(cl <- object$dataClasses)) 
        .checkMFClasses(cl, newdata)
    object$m$predict(newdata)
}
<bytecode: 0xdcc014>
<environment: namespace:stats>


closure環境,評価環境

関数が定義されると,それが定義された環境を含むclosure環境がつくられる.
関数が呼び出されると,その評価 (evaluation) 環境が,closure環境を囲み環境としてつくられる (必ずしもその環境を呼び出した環境ではない).

> a <- rnorm(3)
> foo <- function(x){round(x)}
> environment(foo)
<environment: R_GlobalEnv>
> environment(foo) <- baseenv() # 環境を変更
> a
[1]  0.7547118 -0.6926263 -0.2032858
> foo(a)
[1]  1 -1  0


関数内部である変数が必要になると,評価環境,そのclosure環境,さらにその囲み環境…と順に検索され,大局的環境またはその関数を含むパッケージ環境に至ると,あとはサーチパスに従って規定環境まで検索され,結局みつからなければ最後は空環境にもどり,エラーがでる.

> foo <- function(x){
+   y <- 10
+   x + y
+ }
> foo(1:10)
 [1] 11 12 13 14 15 16 17 18 19 20
> y <- -1
> foo(1:10) # foo の環境内に定義された y が使われる.
 [1] 11 12 13 14 15 16 17 18 19 20
> bar <- function(x){x + y}
> y <- 10
> bar(1:10)
 [1] 11 12 13 14 15 16 17 18 19 20
> y <- -1
> bar(1:10) # グローバル環境に定義された y が使われる.
 [1] 0 1 2 3 4 5 6 7 8 9


スコープ

アクティブな環境へは,呼び出しスタックを通じてアクセスできる.関数が呼び出されるたびにcontextという内部構造がつくられ,スタックされる.関数評価が終わると,contextは呼び出しスタックから取り除かれる.利用できる変数を呼び出しスタックの上部に,時間軸に沿って定義することをdynamic scopeと呼び,関数が定義された環境中の値が使われるのはstatic scope (lexical scope) である.呼び出しスタックへのアクセスには,一連のsys. 関数が使われる.
スコープ規則とは,未拘束変数の値をみつける際に使われる規則群のこと.関数の仮引数は拘束変数,関数内で定義された変数は局所変数,その他は未拘束変数である.未拘束変数に対しては,関数の付属環境 → その囲み環境 → さらにその囲み環境 と,最終的に大局的環境に至るまで検索がなされる.

> foo <- function(x){
+   y <- 10
+   bar <- function(x){x + y + z}
+   return(bar)
+ }
> (foo2 <- foo())
function(x){x + y + z}
<environment: 0xd43440>
> get("bar", envir = environment(foo2))
function(x){x + y + z}
<environment: 0xd43440>
> foo2(1)
 以下にエラー foo2(1) :  オブジェクト 'z' がありません 
> foo2(1, 2)
 以下にエラー foo2(1, 2) :  使われていない引数 (2) 
> z <- 2
> foo(1)
function(x){x + y + z}
<environment: 0xe66f64>
> foo2(1)
[1] 13


関数引数

関数評価の最初のステップで,仮引数と実引数のマッチングが3段階で行われる.
1. 名前タグをもつ仮引数と実引数の完全マッチング (複数該当した場合はエラー).
2. 部分マッチング (複数該当した場合はエラー).
3. 引数位置によるマッチング.
関数引数は値渡し (call-by-value) が原則で,引数は与えられた値で初期化され,仮引数の名前をもつ局所変数であるかのようにふるまう.
関数引数は遅延評価 (lazy evaluation) であり,必要になるまで評価されない.

> foo <- function(x){
+   y <- 1
+   print(y)
+   x * y
+ }
> foo(y <- 0) # 関数内のyは変更されないが,
[1] 1
[1] 0
> y # 大局環境のyは変更されている.
[1] 0


予約オブジェクト

遅延評価のコアとなる特殊なオブジェクト.ある関数が呼び出されたときに.引数が照合され,それぞれの仮引数が予約 (promise) オブジェクトに結びつけられ,仮引数に与えられた表現式と,その関数が呼び出された環境へのポインタが保存される.引数が実際にアクセスされると,保存された表現式が,ポインタの示す環境中で評価され,その値が予約に保存される.以降のアクセスでは,その保存された値が使われる.
関数substitute() は予約から中身の表現式を取り出す.関数delayedAssign() は表現式から予約オブジェクトをつくりだす.

> delayedAssign("x", {y <- 2; cat("These are", y, "numbers.\n"); runif(y)})
> x # 1回目は表現式全体を評価.
These are 2 numbers.
[1] 0.5436929 0.7225412
> x # 2回目は代入された値のみ評価.
[1] 0.5436929 0.7225412

予約は,その値が必要になった時点で強制評価 (forcing) される.

> foo <- function(x, y = deparse(x)){
+   x <- 0
+   print(y) # ここで初めてyが評価される.
+ }
> foo(1)
[1] "0"
> 
> bar <- function(x, y = deparse(x)){
+   y # ここですでにyが評価される.
+   x <- 0
+   print(y)
+ }
> bar(1)
[1] "1"


第2回へつづく.