0Pricing
R Academy · 课时

词法作用域规则

了解 R 如何通过搜索父级环境查找变量

词法作用域规则 是 CoddyKit 上的免费 R Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 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 查找变量时,会按以下顺序搜索环境:函数自身 → 外围环境 → 外围环境的外围环境 → …… → 全局环境 → 搜索路径 → 基础环境 → 空环境。第一个匹配项会被采用。

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')

常见问题解答

「词法作用域规则」课时是免费的吗?

是的 — 「词法作用域规则」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 R Academy 课程的其余内容,请升级到 CoddyKit PRO。 R Academy 课程共包含 4 节课。

「词法作用域规则」这节课中我会学到什么?

了解 R 如何通过搜索父级环境查找变量 你通过在浏览器中直接运行的动手代码来练习 R Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 R Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 R Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「词法作用域规则」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 R Academy 课中编写并运行代码吗?

能。每节 R Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. R 中的环境是什么
  2. 词法作用域规则
  3. 全局作用域与局部作用域
  4. 创建并检查环境
← 返回 R Academy