environment はベクトルと違い参照渡しのように振る舞う
Rのベクトルやリストは関数に渡すと複製され、関数内での変更は呼び出し元に影響しません。一方environmentは参照として渡されるため、関数内でその中身を書き換えると呼び出し元のenvironmentにもその変更が反映されます。
なぜエラーが出ないのか
出力: 10 10
(エラーなし)- Rは何も報告しません。文法としても実行としても正しいコードだからです
10 10- 実際の出力 — 期待した結果と食い違っている箇所
このエラーが出る典型パターン
パターン1
1 make_counter <- function() { 2 e <- new.env() 3 e$value <- 0 4 e 5 } 6 counter1 <- make_counter() 7 counter2 <- counter1 ^ 8 counter2$value <- 10 9 cat(counter1$value, counter2$value, "\n")
counter2 <- counter1はenvironmentの参照をコピーするだけなので、counter2への変更がcounter1にもそのまま反映されます。独立させたい場合はmake_counter()で新しいenvironmentを作る必要があります。
直し方: counter1 を make_counter() にします。
パターン2
1 make_wallet <- function() { 2 e <- new.env() 3 e$balance <- 0 4 e 5 } 6 wallet1 <- make_wallet() 7 wallet2 <- wallet1 ^ 8 wallet2$balance <- 50 9 cat(wallet1$balance, wallet2$balance, "\n")
ベクトルなら代入でコピーが作られますが、environmentは参照が共有されるため、wallet2の変更がwallet1にも及びます。
直し方: wallet1 を make_wallet() にします。
パターン3
1 make_tank <- function() { 2 e <- new.env() 3 e$fuel <- 0 4 e 5 } 6 tank1 <- make_tank() 7 tank2 <- tank1 ^ 8 tank2$fuel <- 20 9 cat(tank1$fuel, tank2$fuel, "\n")
tank2 <- tank1は同じenvironmentを指すだけの別名を作るため、片方への変更がもう片方にも見えてしまいます。
直し方: tank1 を make_tank() にします。
パターン4
1 make_meter <- function() { 2 e <- new.env() 3 e$reading <- 0 4 e 5 } 6 meter1 <- make_meter() 7 meter2 <- meter1 ^ 8 meter2$reading <- 7 9 cat(meter1$reading, meter2$reading, "\n")
独立した2つのメーターを用意したいなら、それぞれでmake_meter()を呼んで別々のenvironmentを作る必要があります。
直し方: meter1 を make_meter() にします。
パターン5
1 make_box <- function() { 2 e <- new.env() 3 e$count <- 0 4 e 5 } 6 box1 <- make_box() 7 box2 <- box1 ^ 8 box2$count <- 3 9 cat(box1$count, box2$count, "\n")
「Rの値は代入すればコピーされるはず」という思い込みはenvironmentには通用せず、box1とbox2は同じ実体を指し続けます。
直し方: box1 を make_box() にします。
よくある誤解
「Rの値はすべて関数に渡すとコピーされ、呼び出し元には影響しないはず」という思い込みはenvironmentには当てはまりません。environmentだけは例外的に参照として共有され、関数内での変更がそのまま呼び出し元に伝わります。
実務での勘所
Rが環境にだけ参照渡りの性質を与えているのは、意図的な設計です。ベクトルやリストのコピーオンモディファイという仕組みは、「他のどこかに影響を与えない」という安心感と引き換えに、複製のコストを払っています。環境はこの複製を行わない例外で、複数の場所から同じ状態を共有し、どこからでも書き換えられるようにするための仕組みです。R6クラスやReference Classes(R5)といった、Rでミュータブルなオブジェクト指向スタイルを実現する仕組みは、まさにこの環境の参照渡りの性質の上に構築されています。