変数のスコープと <<-
『R』では関数の内部で定義した変数はローカルスコープに閉じており、関数の外側には影響しません。親環境の変数を書き換えたい場合は <<- 演算子(スーパーアサインメント)を使います。環境(Environment)オブジェクトを明示的に操作することで、スコープの仕組みをより細かく制御することもできます。
構文
# -----------------------------------------------
# ローカルスコープ(<- は現在の関数環境内に変数を作ります)
# -----------------------------------------------
outer_var <- "外側の変数"
my_func <- function() {
local_var <- "ローカル変数" # 関数スコープ内にのみ存在します
cat(local_var, "\n")
cat(outer_var, "\n") # 親環境の変数は参照できます
}
my_func()
# cat(local_var, "\n") # エラー: local_var は関数の外から見えません
# -----------------------------------------------
# <<- による親環境への代入
# -----------------------------------------------
counter <- 0
increment <- function() {
counter <<- counter + 1 # 親環境の counter を直接書き換えます
}
increment()
increment()
cat(counter, "\n") # 2 が表示されます
# -----------------------------------------------
# environment() と new.env() による環境操作
# -----------------------------------------------
# 新しい環境を作成します
env <- new.env(parent = emptyenv())
assign("score", 100, envir = env) # env 環境に変数を代入します
cat(get("score", envir = env), "\n") # env 環境から値を取得します
主な構文・関数一覧
| 構文 / 関数 | 概要 |
|---|---|
x <- 値 | 現在の関数スコープ(ローカル環境)に変数を作成または上書きします。 |
x <<- 値 | 現在の環境を遡り、x が見つかった最初の親環境に代入します。グローバル環境まで遡っても見つからない場合はグローバルに作成します。 |
environment() | 現在の関数が実行されている環境オブジェクトを返します。引数なしで呼び出すと呼び出し元の環境を返します。 |
parent.env(env) | 指定した環境の親環境を返します。 |
new.env(parent = ...) | 新しい環境オブジェクトを作成します。parent で親環境を指定します。 |
assign(name, value, envir = ...) | 指定した環境に変数を代入します。文字列でキー名を渡せます。 |
get(name, envir = ...) | 指定した環境から変数の値を取得します。 |
ls(envir = ...) | 指定した環境に存在する変数名の一覧を返します。 |
exists(name, envir = ...) | 指定した環境に変数が存在するかを TRUE / FALSE で返します。 |
local({ ... }) | 新しいローカル環境を生成して式を評価します。一時変数を外に漏らしたくないときに使います。 |
サンプルコード
kof_function_scope.R
# kof_function_scope.R — 変数スコープと <<- を確認するサンプルです
# THE KING OF FIGHTERS のキャラクターデータを使って
# ローカルスコープ・<<- による親環境代入・環境オブジェクト操作を確認します
cat("=== THE KING OF FIGHTERS — スコープ検証 ===\n\n")
# -----------------------------------------------
# ローカルスコープの確認
# -----------------------------------------------
# グローバル環境に存在するチーム名です
team_name <- "餓狼伝説チーム"
# 関数内で同名の変数を作成しても、グローバル変数は上書きされません
check_scope <- function() {
team_name <- "関数内のチーム名" # ローカル変数を作成します
cat(sprintf(" 関数内の team_name: %s\n", team_name))
}
cat("--- ローカルスコープ ---\n")
check_scope()
# 関数実行後もグローバル変数は変化しません
cat(sprintf(" グローバルの team_name: %s\n", team_name))
cat("\n")
# -----------------------------------------------
# <<- による親環境への代入
# -----------------------------------------------
# 参戦回数をグローバル変数で管理します
entry_count <- 0
# 1 人参戦するたびに entry_count を増やす関数です
register_fighter <- function(name) {
entry_count <<- entry_count + 1 # <<- でグローバルの entry_count を更新します
cat(sprintf(" [%d] %s が参戦登録されました。\n", entry_count, name))
}
cat("--- <<- による親環境への代入 ---\n")
register_fighter("草薙京")
register_fighter("八神庵")
register_fighter("テリー・ボガード")
register_fighter("クーラ・ダイアモンド")
register_fighter("ヴァネッサ")
cat(sprintf(" 合計参戦人数: %d 名\n", entry_count))
cat("\n")
# -----------------------------------------------
# クロージャで <<- を活用する
# -----------------------------------------------
# 勝利数カウンターをクロージャで実装します
make_win_counter <- function(fighter_name) {
wins <- 0 # クロージャ内でローカルに管理します
list(
# add_win は wins を <<- で更新します(make_win_counter の環境を参照します)
add_win = function() {
wins <<- wins + 1
},
# get_wins は現在の勝利数を返します
get_wins = function() {
cat(sprintf(" %s の勝利数: %d 勝\n", fighter_name, wins))
}
)
}
cat("--- クロージャと <<- ---\n")
# 各キャラクター専用のカウンターを作成します
kyo_counter <- make_win_counter("草薙京")
iori_counter <- make_win_counter("八神庵")
terry_counter <- make_win_counter("テリー・ボガード")
# 試合結果を反映します
kyo_counter$add_win()
kyo_counter$add_win()
kyo_counter$add_win()
iori_counter$add_win()
iori_counter$add_win()
terry_counter$add_win()
# 各カウンターは互いに独立しています
kyo_counter$get_wins()
iori_counter$get_wins()
terry_counter$get_wins()
cat("\n")
# -----------------------------------------------
# environment() と new.env() による環境操作
# -----------------------------------------------
cat("--- environment() と new.env() ---\n")
# 専用の環境オブジェクトを作成して選手データを格納します
fighter_env <- new.env(parent = emptyenv())
# assign() で環境に変数を代入します
assign("kyo", list(name = "草薙京", power = 92), envir = fighter_env)
assign("iori", list(name = "八神庵", power = 88), envir = fighter_env)
assign("terry", list(name = "テリー・ボガード", power = 85), envir = fighter_env)
# ls() で環境内の変数名一覧を取得します
fighter_keys <- ls(envir = fighter_env)
cat(sprintf(" fighter_env に登録されたキー: %s\n", paste(fighter_keys, collapse = ", ")))
# get() で環境から値を取り出します
for (key in sort(fighter_keys)) {
fighter <- get(key, envir = fighter_env)
cat(sprintf(" %-6s → %s (戦闘力: %d)\n", key, fighter$name, fighter$power))
}
cat("\n")
# -----------------------------------------------
# local() による一時的なローカル環境
# -----------------------------------------------
cat("--- local() による一時スコープ ---\n")
# local() ブロック内の変数はブロック外に漏れません
final_result <- local({
# tmp_scores はこのブロック内にのみ存在します
tmp_scores <- c(
kyo = 95,
iori = 89,
terry = 82,
kula = 91,
vanessa = 78
)
winner_name <- names(which.max(tmp_scores)) # 最高スコアのキーを取得します
winner_score <- max(tmp_scores)
sprintf("%s(スコア: %d)", winner_name, winner_score) # ブロックの最後の値が返ります
})
cat(sprintf(" トーナメント優勝: %s\n", final_result))
# tmp_scores はグローバル環境に存在しません
cat(sprintf(" tmp_scores は存在するか: %s\n", exists("tmp_scores")))
cat("\n")
Rscript kof_function_scope.R === THE KING OF FIGHTERS — スコープ検証 === --- ローカルスコープ --- 関数内の team_name: 関数内のチーム名 グローバルの team_name: 餓狼伝説チーム --- <<- による親環境への代入 --- [1] 草薙京 が参戦登録されました。 [2] 八神庵 が参戦登録されました。 [3] テリー・ボガード が参戦登録されました。 [4] クーラ・ダイアモンド が参戦登録されました。 [5] ヴァネッサ が参戦登録されました。 合計参戦人数: 5 名 --- クロージャと <<- --- 草薙京 の勝利数: 3 勝 八神庵 の勝利数: 2 勝 テリー・ボガード の勝利数: 1 勝 --- environment() と new.env() --- fighter_env に登録されたキー: iori, kyo, terry iori → 八神庵 (戦闘力: 88) kyo → 草薙京 (戦闘力: 92) terry → テリー・ボガード (戦闘力: 85) --- local() による一時スコープ --- トーナメント優勝: kyo(スコア: 95) tmp_scores は存在するか: FALSE
概要
『R』の変数スコープはレキシカルスコープ(静的スコープ)に基づいており、関数は定義された時点の環境を記憶します。関数内で <- を使って代入すると変数はそのローカルスコープに閉じ、関数の外側には影響しません。外側の変数を書き換えたい場合は <<-(スーパーアサインメント演算子)を使います。<<- は現在の環境から親環境を順に遡り、同名の変数が最初に見つかった環境に代入します。この仕組みはクロージャと組み合わせることで、関数を呼び出すたびに状態を保持するカウンターや累積処理を実装できます。new.env() で環境オブジェクトを明示的に生成し、assign() や get() で操作することも可能で、名前空間の分離や動的なキー管理に役立ちます。また local() を使うと一時変数をグローバル環境に漏らさずに処理をまとめられます。スコープの理解は 関数の基本 や 戻り値 と合わせて確認するとより深まります。
よくあるミス
<<- でグローバル変数を意図せず書き換える
<<- は親環境(通常はグローバル環境)に代入します。関数内でグローバル変数を書き換えると、副作用が生じてデバッグが難しくなります。
scope_superassign.R
counter <- 0
increment <- function() {
counter <<- counter + 1 # グローバルの counter を書き換える
}
increment()
increment()
cat("Counter:", counter, "\n") # 2
Counter: 2
状態を管理する必要がある場合は、戻り値を使うか R5 クラス(Reference Classes)を検討します。
関数内の同名ローカル変数がグローバル変数を隠す
関数内で同名の変数に代入すると、グローバル変数とは別のローカル変数が作られます。関数内からはローカル変数が優先されます。
scope_shadow.R
name <- "Son Goku"
change_name <- function() {
name <- "Vegeta" # ローカル変数(グローバルとは別)
cat("Inside:", name, "\n")
}
change_name()
cat("Outside:", name, "\n") # グローバルは変わっていない
Inside: Vegeta Outside: Son Goku
クロージャのループ変数キャプチャ
ループの中でクロージャを作ると、クロージャはループ変数の「値」ではなく「参照」をキャプチャします。ループが終わった後に関数を呼び出すと、すべて最後の値を参照します。
scope_closure.R
# クロージャを生成してすぐに呼び出すと正しい値が得られる
make_greeting <- function(name) {
function() cat("Hello,", name, "!\n")
}
greetings <- lapply(c("Son Goku", "Vegeta", "Gohan"), make_greeting)
# 各クロージャは独自の name をキャプチャしている
greetings[[1]]()
greetings[[2]]()
greetings[[3]]()
Hello, Son Goku ! Hello, Vegeta ! Hello, Gohan !
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。