レキシカルスコープの規則
親Environmentを検索してRが変数を見つける仕組みを理解します。
「レキシカルスコープの規則」はCoddyKit上の無料R Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはR Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 R Academyコースには全4レッスンが含まれています。
レキシカルスコープとは
レキシカルスコープとは、関数が変数を呼び出された場所ではなく、定義された場所で検索する仕組みです。Rはレキシカルスコープを使用します。関数が検索する環境は呼び出し時ではなく作成時に決まるため、動作を予測しやすくなります。
x <- 'global'
f <- function() {
cat('x =', x, '\n') # looks where f was defined (global)
}
local({
x <- 'local to local block'
f() # still prints global x, not the local one
})レキシカルスコープと動的スコープ
動的スコープ(一部の他の言語で使われます)では、関数は呼び出し元の環境で変数を検索します。一方、Rはレキシカルスコープを使うため、定義時の包含環境が常に優先されます。そのため、どこから呼び出しても関数は一貫して動作します。
val <- 100
make_adder <- function(n) {
# n is captured from the definition environment
function(x) x + n
}
add10 <- make_adder(10)
add20 <- make_adder(20)
cat(add10(5), '\n') # 15 — n=10 from definition
cat(add20(5), '\n') # 25 — n=20 from definition自由変数とクロージャ
関数内にある変数のうち、引数でもローカルな代入でもないものを自由変数と呼びます。Rはその変数を包含環境で検索します。自由変数を取り込む関数はクロージャと呼ばれます。
base_rate <- 0.05 # free variable captured by f
f <- function(principal, years) {
principal * (1 + base_rate) ^ years
}
cat('5 yr return:', f(1000, 5), '\n')
# Even if we change base_rate later:
base_rate <- 0.08
cat('New rate return:', f(1000, 5), '\n')クロージャによる環境の取り込み
ファクトリ関数が呼び出されるたびに、返される関数(クロージャ)は包含環境のスナップショットを取り込みます。呼び出しごとに、異なる値を取り込んだクロージャが生成されます。これが関数ファクトリの基礎です。
power_fn <- function(exp) {
function(x) x ^ exp
}
square <- power_fn(2)
cube <- power_fn(3)
cat('square(4) =', square(4), '\n')
cat('cube(3) =', cube(3), '\n')<<- 演算子
<<-(スーパー代入)は、親チェーンを上にたどって既存のバインディングを探し、その場で変更します。バインディングが見つからない場合は、グローバル環境に作成します。関数内から包含スコープの変数を変更するための演算子です。
count <- 0
increment <- function() {
count <<- count + 1
}
increment()
increment()
increment()
cat('count:', count, '\n')クロージャでの <<-
<<-は、包含環境の状態を更新する必要があるクロージャ内で使うのが最も慣用的です。これにより、呼び出しの間も状態を保持するステートフルな関数を作成できます。
make_counter <- function() {
n <- 0
list(
increment = function() n <<- n + 1,
get = function() n,
reset = function() n <<- 0
)
}
ctr <- make_counter()
ctr$increment()
ctr$increment()
cat('Count:', ctr$get(), '\n')
ctr$reset()
cat('After reset:', ctr$get(), '\n')入れ子になった関数のスコープ
関数が入れ子になっている場合、内側の関数から外側の関数で定義された変数を参照できます。外側の関数の環境が内側の関数の包含環境になり、チェーンが形成されます。
outer <- function() {
greeting <- 'Hello'
inner <- function(name) {
# greeting comes from outer's environment
paste(greeting, name)
}
inner('World')
}
cat(outer(), '\n')スコープチェーンの検索
Rが変数を検索するときは、関数自身の環境 → 包含環境 → 包含環境の包含環境 → … → グローバル環境 → 検索パス → base → empty の順に環境を検索します。最初に見つかったものが使われます。
x <- 'global'
f <- function() {
x <- 'local to f'
g <- function() {
# g has no x, so looks in f's env
cat('g sees x =', x, '\n')
}
g()
}
f()関数の environment()
environment(f)を使うと、任意の関数の包含環境を調べられます。environment(f) <- eで環境を設定することもできます。これにより、関数が自由変数を検索する場所を変更できますが、高度なテクニックです。
x <- 'global_x'
f <- function() x
cat('f sees:', f(), '\n')
# Change f's enclosing environment
my_env <- new.env(parent = emptyenv())
my_env$x <- 'env_x'
environment(f) <- my_env
cat('f now sees:', f(), '\n')実践的なクロージャ:メモ化
レキシカルスコープと<<-を組み合わせると、メモ化を実現できます。計算コストの高い関数の結果をクロージャの環境にキャッシュします。キャッシュはクロージャからのみ利用でき、呼び出しの間も保持されます。
make_memoized_fib <- function() {
cache <- c()
function(n) {
if (!is.null(cache[n])) return(cache[n])
if (n <= 1) { cache[n] <<- n; return(n) }
result <- Recall(n-1) + Recall(n-2)
cache[n] <<- result
result
}
}
fib <- make_memoized_fib()
cat(sapply(0:8, fib), '\n')レキシカルスコープの落とし穴:ループ変数
典型的な落とし穴は、ループ内でクロージャを作成することです。すべてのクロージャが値のスナップショットではなく、同じ環境を取り込みます。そのため、呼び出される時点ではループ変数が最後の値になっています。
fns <- vector('list', 3)
for (i in 1:3) {
local({
captured_i <- i
fns[[captured_i]] <<- function() captured_i
})
}
cat(fns[[1]](), fns[[2]](), fns[[3]](), '\n')確認問題
Rのレキシカルスコープでは、自由変数(関数自体で定義されていない変数)を関数はどこで検索しますか。
レキシカルスコープ:重要なポイント
レキシカルスコープに関する重要なポイント:
- Rはレキシカルスコープを使います。関数は変数を定義された場所で検索します
- 関数は包含環境をクロージャとして取り込みます
- 自由変数は呼び出し時ではなく定義時に解決されます
<<-は親チェーン内のバインディングを変更します(見つからない場合はグローバル環境を変更します)- クロージャ +
<<-= 永続的なプライベート状態を持つステートフルな関数 - ループ内のクロージャの落とし穴を避けるには、
local()を使って反復ごとに新しい環境を作成します
make_multiplier <- function(factor) {
function(x) x * factor
}
double <- make_multiplier(2)
triple <- make_multiplier(3)
cat('double(7):', double(7), '\n')
cat('triple(7):', triple(7), '\n')
cat('double env factor:', get('factor', envir = environment(double)), '\n')よくある質問
「レキシカルスコープの規則」レッスンは無料ですか?
はい。「レキシカルスコープの規則」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、R Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 R Academyコースには全4レッスンが含まれています。
「レキシカルスコープの規則」で何を学びますか?
親Environmentを検索してRが変数を見つける仕組みを理解します。 ブラウザで直接実行するハンズオンコードでR Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
R Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのR Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「レキシカルスコープの規則」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このR Academyレッスンでコードを書いて実行できますか?
はい。すべてのR Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。