Mukku John Blog

取り組んでいること を つらつら と

Rプログラミング入門 13回目

環境 後編

この記事の後編。
mukkujohn.hatenablog.com

関数実行時の環境はどうなっているのか?の続き。

評価

Rは、関数を実行する際に、

  1. 新しい環境を作り、
  2. 作った環境で関数を実行し、
  3. 関数が呼び出された環境に戻ります。

この関数で確認してみます。

> 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関数を実行時のイメージがこちら。
f:id:MukkuJohn:20160714220618p:plain
(→の向きが、親から子に向いていますが、作成している事を示しています。)

この親環境子環境ですが、
関数が初めて作成された環境が親環境になります。

show_env関数は、コマンドラインから作成されたので、
R_GlobalEnv親環境であり、また、初めて作成された環境を
オリジン環境と呼びます。
(この本翻訳大丈夫かなぁ。)
f:id:MukkuJohn:20160714222140p:plain

子環境は、関数を実行する度に作られる環境ですので、実行時環境と呼びます。
f:id:MukkuJohn:20160714222525p:plain

このオリジン環境ですが、関数が初めて作成された環境ですので、
必ずしも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

実行時環境に、abcの3つのオブジェクトが作成されています。

このようなイメージですね。
f:id:MukkuJohn:20160717194459p:plain

次に、関数に引数を渡した時に、オブジェクトがどのようになっているか
再度、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オブジェクトを実行時環境に生成し、
引数の値をコピーします。

このようなイメージです。
f:id:MukkuJohn:20160717204435p:plain

イメージ内に記載しましたが、関数を呼び出す環境を、呼び出し元環境と呼びます。

呼び出し元環境は、必ずしもオリジン環境ではない事に、注意が必要です。
今回の例では、関数の作成環境 = オリジン環境が、
関数を呼ぶ環境 = 呼び出し元環境となっているだけです。

まとめると、このイメージになります。
f:id:MukkuJohn:20160717210103p:plain
今まで、オリジン環境呼び出し元環境も、R_GlobalEnvであり、
それぞれの環境の違いが分かりづらかったかもしれません。

さて、このdeal関数ですが、
引数を取らずに、deckオブジェクトを操作しています。

> deal <- function(){
+ deck[1, ]
+ }

environment関数で、環境を確認すると、R_GlobalEnvです。

> environment(deal)
<environment: R_GlobalEnv>

関数呼び出し時のイメージはこれです。
f:id:MukkuJohn:20160717212558p:plain
呼び出し元環境オリジン環境が同じです。

この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オブジェクトに、新しい値を割り当てます。
f:id:MukkuJohn:20160717213250p:plain

関数終了時に、呼び出し元環境に戻ると、関数実行時に新しい値を
割り当てられたdeckオブジェクトになっています。
f:id:MukkuJohn:20160717213340p:plain

数回実行してみます。

> 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)

ですが、これには問題があります。

  1. R_Global_Env環境にあるdeckオブジェクトをshuffleしていない
  2. 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環境に、deckDECKオブジェクトが無いと動きません。

別にいいかな?と思いますが、時分の責任が持てる範囲でオブジェクトは管理するべきです。

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

イメージはこうです。
f:id:MukkuJohn:20160717222621p:plain

ここで重要なのが、dealshuffleの関数の環境です。

> environment(deal)
<environment: 0x000000000291cb88>
> environment(shuffle)
<environment: 0x000000000291cb88>

今まで、関数を初めて作成した環境、すなわち、オリジン環境が、
R_Global_Env環境であったのが、異なる環境になっています。

(もちろんsetup関数の親環境はR_Global_Env環境です。)

> environment(setup)
<environment: R_GlobalEnv>

イメージを見て頂くとわかると思いますが、
DEALSHUFFLE関数のオリジン環境は、setup関数の実行時環境です。

そして、deckDECKオブジェクトが、R_Global_Env環境から隔離されています。
この構成をクロージャと言います。

この構成を取れば、DEALSHUFFLE関数が参照する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

dealshuffleオブジェクトに割り当てたDEALSHUFFLE関数は
オリジン環境にある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環境のオブジェクトに左右されない関数を作る事ができました。

(長かった。。。イメージ作るのが。。。文章だけでこれ伝えるスキルが欲しい。)