0Pricing
R Academy · Урок

Правила лексической области видимости

Разберитесь, как R находит переменные, выполняя поиск в родительских окружениях.

«Правила лексической области видимости» — бесплатный урок R Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения 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 проверяет окружения в таком порядке: собственное окружение функции → объемлющее окружение → окружение, объемлющее его → ... → глобальное окружение → путь поиска → базовое окружение → пустое окружение. Используется первое найденное совпадение.

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/7) и разблокировать остальной курс R Academy, подпишись на CoddyKit PRO. Курс R Academy содержит 4 уроков всего.

Чему я научусь в уроке «Правила лексической области видимости»?

Разберитесь, как R находит переменные, выполняя поиск в родительских окружениях. Ты практикуешь R Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать R Academy?

Предыдущий опыт не требуется. R Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Правила лексической области видимости»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке R Academy?

Да. Каждый урок R Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Что такое окружение в R?
  2. Правила лексической области видимости
  3. Глобальная и локальная область видимости
  4. Создание и проверка окружений
← Назад к R Academy