環境 (その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回へつづく.