Rのスコープ規則でハマったので調べた、というお話
motivation
ある関数の中で、データフレームをいじくり回したかった。が、いちいち [ 演算子とか $ 演算子とかでアクセスすると、コードが見辛くなってやだ、と思った。例えば、
plot(dat$x[dat$label=="a"], dat$y[dat$label=="a"])
みたいなコードは書きたくない。
最初の失敗
最初は、attachすればいいんじゃね?と思った。が、これはうまくいかない。
> x <- 1 > y <- "a" > > f1 <- function(){ + dfr <- data.frame(x=1:10, y=11:20) + attach(dfr) ## attach では、serach() ベクトルの.GlobalEnvの後ろにdfrが追加される。 + on.exit(detach(dfr)) + cat("x=", x, "\ny=", y, "\n", sep=" ") ## dfr は search() の中で、.Globalより後ろだから、この x, y は .GlobalEnvの x, y。 + } > f1() The following object(s) are masked _by_ .GlobalEnv : x y x= 1 y= a
Rのスコープ規則では、ある関数呼び出しの中で変数名を解決する必要がある時には、
- その関数呼び出しに付随する環境
- その関数を囲む環境(その関数が定義された環境。呼び出し元の環境とは違う。cf. 静的スコープ)
- その環境を囲む環境を囲む環境。
- ...
- グローバル環境
- search() ベクトルの第2要素(第1要素は.GlobalEnv)
- ...
- package:base (search() ベクトルの最終要素)
と探されるんだが、attachは、このsearch() ベクトルのどっかにデータフレームを登録する関数であるからだ。
成功例
以下のように、assignすれば上手く行くことが分かった。
> x <- 1 > y <- "a" > > > f2 <- function(){ + dfr <- data.frame(x=1:10, y=11:20) + env <- environment() ## environment() で環境を取得して + sapply(names(dfr), function(x)assign(x, dfr[ ,x], env=env)) ## その環境に附置してやる。 + cat("x=", x, "\ny=", y, "\n", sep=" ") + } > f2() x= 1 2 3 4 5 6 7 8 9 10 y= 11 12 13 14 15 16 17 18 19 20
今後の課題
取り敢えず今回の例は解決したんだけど、色々調べていて、まだきちんと理解出来ていない挙動があったので、自分用にメモを残す。以下の例では、as.environment(-1)の結果が、in.func の中で呼ばれた時と、printの引数の中で呼ばれた時で違う。
> f3 <- function(){ + cat(1); print(environment()) + in.func <- function(){ + par.env <- as.environment(-1) + cat(2); print(par.env) + this.env <- environment() + cat(3); print(this.env) + cat(4); print(as.environment(-1)) + cat(5); print(environment()) + } + in.func() + } > f3() 1<environment: 0x100f9dd10> 2<environment: 0x100f9dd10> 3<environment: 0x100f9b8e8> 4<environment: 0x100f9b8e8> 5<environment: 0x100f9b8e8>
次のような例と関係があるのかしら。この例では、y=echo(x) は、f4に付随する環境でなく、in.func(10) という呼び出しに付随する環境で評価されている。
> f4 <- function(){ + x <- 1 + echo <- function(x){ + x + } + in.func <- function(x, y=echo(x)){ + print(y) + } + in.func(10) + } > f4() [1] 10
さらに複雑にしてみてよく分からない例。この例の f6 の出力 2 に着目すると、どうやら as.environment(-1)は、静的スコープの意味での親環境では無くて、動的スコープが辿る実行時の親子関係の意味での親環境を返すようである。
> f5 <- function(){ + function(x){ + env.1 <- environment() + cat(1); print(env.1) + env.2 <- as.environment(-1) + cat(2); print(env.2) + cat(3); print(x) + } + } > (f6 <- f5()) function(x){ env.1 <- environment() cat(1); print(env.1) env.2 <- as.environment(-1) cat(2); print(env.2) cat(3); print(x) } <environment: 0x100e18388> > f6(as.environment(-1)) 1<environment: 0x100e17270> 2<environment: R_GlobalEnv> 3<environment: 0x100e17270> > f6(environment()) 1<environment: 0x100e118e8> 2<environment: R_GlobalEnv> 3<environment: R_GlobalEnv> > > f7 <- function(){ + env.1 <- environment() + cat(4); print(env.1) + env.2 <- as.environment(-1) + cat(5); print(env.2) + print("f6(as.environment(-1))") + f6(as.environment(-1)) + print("f6(environment())") + f6(environment()) + } > f7() 4<environment: 0x100e0af10> ## f7()に付随する環境 5<environment: R_GlobalEnv> ## f7()を呼び出した環境 [1] "f6(as.environment(-1))" 1<environment: 0x100e05a08> ## f6(as.environment(-1))に付随する環境 2<environment: 0x100e0af10> ## f6(as.environment(-1))を呼び出した環境 = f7()に付随する環境 3<environment: 0x100e05a08> ## f6 の引数として評価された as.environment(-1) の返り値 [1] "f6(environment())" 1<environment: 0x100dffce0> ## f6(environment()) に付随する環境 2<environment: 0x100e0af10> ## f6(environment()) を呼び出した環境 = f7()に付随する環境 3<environment: 0x100e0af10> ## f6 の引数として評価された environment() の返り値 = f7()に付随する環境
まだちょっとよく分かってない部分もあるけど、もうこれ以上は頭使いたくなくなってきたので、そのうちRプログラミングマニュアル読もう。