Code Fix

上級

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")
出力: 10 10

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")
出力: 50 50

ベクトルなら代入でコピーが作られますが、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")
出力: 20 20

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")
出力: 7 7

独立した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")
出力: 3 3

「Rの値は代入すればコピーされるはず」という思い込みはenvironmentには通用せず、box1とbox2は同じ実体を指し続けます。

直し方: box1 を make_box() にします。

この問題を解いてみる →

よくある誤解

「Rの値はすべて関数に渡すとコピーされ、呼び出し元には影響しないはず」という思い込みはenvironmentには当てはまりません。environmentだけは例外的に参照として共有され、関数内での変更がそのまま呼び出し元に伝わります。

実務での勘所

Rが環境にだけ参照渡りの性質を与えているのは、意図的な設計です。ベクトルやリストのコピーオンモディファイという仕組みは、「他のどこかに影響を与えない」という安心感と引き換えに、複製のコストを払っています。環境はこの複製を行わない例外で、複数の場所から同じ状態を共有し、どこからでも書き換えられるようにするための仕組みです。R6クラスやReference Classes(R5)といった、Rでミュータブルなオブジェクト指向スタイルを実現する仕組みは、まさにこの環境の参照渡りの性質の上に構築されています。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)