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プログラミングマニュアル読もう。