Rプログラミング入門 13回目
環境 後編
この記事の後編。
mukkujohn.hatenablog.com
関数実行時の環境はどうなっているのか?の続き。
評価
Rは、関数を実行する際に、
- 新しい環境を作り、
- 作った環境で関数を実行し、
- 関数が呼び出された環境に戻ります。
この関数で確認してみます。
> show_env <- function(){ list(ran.in = environment(), parent = parent.env(environment()), objects = ls.str(environment())) }
関数の出力結果は、↓です。
- ran.inは関数が実行されている環境
- parentは関数の親環境
- objectsは関数が実行されている環境に存在するオブジェクト
show_env関数を1回実行してみます。
> show_env() $ran.in <environment: 0x00000000126989f0> $parent <environment: R_GlobalEnv> $objects
再度、show_env関数を実行してみます。
> show_env() $ran.in <environment: 0x00000000126acb38> $parent <environment: R_GlobalEnv> $objects
それぞれ環境が異なっているのが確認できました。
また、親の環境がR_GlobalEnvであることも確認できます。
show_env関数を実行時のイメージがこちら。
(→の向きが、親から子に向いていますが、作成している事を示しています。)
この親環境、子環境ですが、
関数が初めて作成された環境が親環境になります。
show_env関数は、コマンドラインから作成されたので、
R_GlobalEnvが親環境であり、また、初めて作成された環境を
オリジン環境と呼びます。
(この本翻訳大丈夫かなぁ。)
子環境は、関数を実行する度に作られる環境ですので、実行時環境と呼びます。
このオリジン環境ですが、関数が初めて作成された環境ですので、
必ずしもR_GlobalEnvが、オリジン環境とは限りません。
試しに、前回の記事で利用したparenvs関数を確認してみます。
> environment(parenvs) <environment: namespace:pryr>
parenvs関数のオリジン環境は、pryrパッケージになります。
次に、関数内で作成するオブジェクトを確認するために、show_env関数を変更します。
> show_env <- function(){ a <- 1 b <- 2 c <- 3 list(ran.in = environment(), parent = parent.env(environment()), objects = ls.str(environment())) }
このshow_env関数を実行してみます。
> show_env() $ran.in <environment: 0x000000000c6aa8a0> $parent <environment: R_GlobalEnv> $objects a : num 1 b : num 2 c : num 3
実行時環境に、a、b、cの3つのオブジェクトが作成されています。
このようなイメージですね。
次に、関数に引数を渡した時に、オブジェクトがどのようになっているか
再度、show_env関数を変更します。
> show_env <- function(x = foo){ list(ran.in = environment(), parent = parent.env(environment()), objects = ls.str(environment())) }
このshow_env関数をこの様に、呼び出してみます。
> foo <- "take me to your runtime" > show_env(foo) $ran.in <environment: 0x000000001257ae48> $parent <environment: R_GlobalEnv> $objects x : chr "take me to your runtime"
show_env関数の実行時環境にxオブジェクトを実行時環境に生成し、
引数の値をコピーします。
このようなイメージです。
イメージ内に記載しましたが、関数を呼び出す環境を、呼び出し元環境と呼びます。
呼び出し元環境は、必ずしもオリジン環境ではない事に、注意が必要です。
今回の例では、関数の作成環境 = オリジン環境が、
関数を呼ぶ環境 = 呼び出し元環境となっているだけです。
まとめると、このイメージになります。
今まで、オリジン環境も呼び出し元環境も、R_GlobalEnvであり、
それぞれの環境の違いが分かりづらかったかもしれません。
さて、このdeal関数ですが、
引数を取らずに、deckオブジェクトを操作しています。
> deal <- function(){ + deck[1, ] + }
environment関数で、環境を確認すると、R_GlobalEnvです。
> environment(deal) <environment: R_GlobalEnv>
関数呼び出し時のイメージはこれです。
呼び出し元環境とオリジン環境が同じです。
このdeal関数ですが、実行するとdeckオブジェクトの1行目の要素を返却します。
> deal() face suit value 1 king spades 13
ですが、本来の意図としては、実行するたびにdealした要素を
deckオブジェクトから除きたいです。
ただ、呼び出し元環境とオリジン環境が利用すると容易に行えます。
deal関数をこの様に変更します。
> deal <- function(){ card <- deck[1, ] assign("deck", deck[-1, ], envir = globalenv()) card }
deal関数の実行時環境の親環境である、オリジン環境に存在する
deckオブジェクトに、新しい値を割り当てます。
関数終了時に、呼び出し元環境に戻ると、関数実行時に新しい値を
割り当てられたdeckオブジェクトになっています。
数回実行してみます。
> deal() face suit value 1 king spades 13 > deal() face suit value 2 queen spades 12 > deal() face suit value 3 jack spades 11
dealした要素が、deckオブジェクトから除かれるようにできました。
次に、このshuffle関数を変更します。
> shuffle <- function(cards){ random <- sample(1:52, size = 52) cards[random, ] }
このshuffle関数ですが、この様に呼び出します。
> a <- shuffle(deck)
ですが、これには問題があります。
- R_Global_Env環境にあるdeckオブジェクトをshuffleしていない
- R_Global_Env環境にあるdeckオブジェクトはdealされている
上記の問題を解決するために、この様に変更します。
shuffle <- function(){ random <- sample(1:52, size = 52) assign("deck", DECK[random, ], envir = globalenv()) }
(DECKオブジェクトは、dealする前のdeckオブジェクトのコピーです)
この変更により、shuffle関数を実行するたび、
オリジン環境にあるdeckオブジェクトに新しい値が割り当てられて、
呼び出し元環境に戻ると、新しい値が割り当てられたdeckオブジェクトが使えます。
> shuffle() > deal() face suit value 2 queen spades 12 > deal() face suit value 25 two clubs 2
> shuffle() > deal() face suit value 51 two hearts 2 > deal() face suit value 49 four hearts 4
ですが、まだこれには欠点があります。
R_GlobalEnv環境に、deck、DECKオブジェクトが無いと動きません。
別にいいかな?と思いますが、時分の責任が持てる範囲でオブジェクトは管理するべきです。
R_GlobalEnv環境では、いつ値が書き換わるか分からないですし、
そもそもオブジェクトが削除されるかもしれないのでなんとかしたいと思います。
クロージャ
そこで、deckオブジェクトを、意図した環境に閉じ込めます。
こちらのsetup関数をみてください。
setup <- function(deck){ DECK <- deck DEAL <- function(){ card <- deck[1, ] assign("deck", deck[-1, ], envir = globalenv()) card } SHUFFLE <- function(){ random <- sample(1:52, size = 52) assign("deck", DECK[random, ], envir = globalenv()) } list(deal = DEAL, shuffle = SHUFFLE) }
呼び出しと、返却はこの様になります。
cards <- setup(deck) deal <- cards$deal shuffle <- cards$shuffle
イメージはこうです。
ここで重要なのが、deal、shuffleの関数の環境です。
> environment(deal) <environment: 0x000000000291cb88> > environment(shuffle) <environment: 0x000000000291cb88>
今まで、関数を初めて作成した環境、すなわち、オリジン環境が、
R_Global_Env環境であったのが、異なる環境になっています。
(もちろんsetup関数の親環境はR_Global_Env環境です。)
> environment(setup) <environment: R_GlobalEnv>
イメージを見て頂くとわかると思いますが、
DEAL、SHUFFLE関数のオリジン環境は、setup関数の実行時環境です。
そして、deck、DECKオブジェクトが、R_Global_Env環境から隔離されています。
この構成をクロージャと言います。
この構成を取れば、DEAL、SHUFFLE関数が参照するdeckオブジェクトは
オリジン環境 = setup関数の実行時環境のdeckオブジェクトなので、
R_Global_Env環境にあるdeckオブジェクトに結果を左右されません。
ただ、上のsetup関数は、
assign関数を利用して、R_Global_Env環境にあるdeakオブジェクトを操作しています。
その部分を変更します。
setup <- function(deck){ DECK <- deck DEAL <- function(){ card <- deck[1, ] assign("deck", deck[-1, ], envir = parent.env(environment())) card } SHUFFLE <- function(){ random <- sample(1:52, size = 52) assign("deck", DECK[random, ], envir = parent.env(environment())) } list(deal = DEAL, shuffle = SHUFFLE) }
変更部分は、assign関数のdeckオブジェクトの場所を示す部分です。
setup関数の実行時環境に存在する、引数の値をコピーしたdeckオブジェクトを指します。
上記の変更後、再度、setup関数に、
R_Global_Env環境にあるdeakオブジェクトを渡して実行します。
cards <- setup(deck) deal <- cards$deal shuffle <- cards$shuffle
deal、shuffleオブジェクトに割り当てたDEAL、SHUFFLE関数は
オリジン環境にあるdeckオブジェクトを参照するため、
R_Global_Env環境にあるdeakオブジェクトを削除しても影響が出ません。
試しに、rm関数でdeakオブジェクト消してから実行してみます。
> rm(deck) > shuffle() > deal() face suit value 28 queen diamonds 12 > deal() face suit value 31 nine diamonds 9
ほらね。
これでようやくR_Global_Env環境のオブジェクトに左右されない関数を作る事ができました。
(長かった。。。イメージ作るのが。。。文章だけでこれ伝えるスキルが欲しい。)