为什么必须使用两轮表达式引用来定义 local({...})?

Why must local({...}) be defined using two rounds of expression quoting?

我想了解 R 的 local 函数是如何工作的。有了它,您可以打开一个临时的本地范围,这意味着 local 中发生的事情(最值得注意的是,变量定义)保留在 local 中。只有区块的最后一个值被返回给外界。所以:

x <- local({
    a <- 2
    a * 2
}) 

x
## [1] 4

a
## Error: object 'a' not found

local 定义如下:

local <- function(expr, envir = new.env()){
    eval.parent(substitute(eval(quote(expr), envir)))
}

据我了解,发生了两轮表达式引用和后续评估:

  1. eval(quote([whatever expr input]), [whatever envir input])substitute 作为未评估的调用生成。
  2. 调用在 local 的调用框架(在我们的例子中是全局环境)中进行评估,所以 [whatever expr input][whatever envir input]
  3. 中计算

但是,我不明白为什么第2步是必要的。为什么我不能像这样简单地定义 local

local2 <- function(expr, envir = new.env()){
    eval(quote(expr), envir)
}

我认为它会在空环境中计算表达式 expr?所以 expr 中定义的任何变量都应该存在于 envir 中,因此在 local2?

结束后消失

但是,如果我尝试这样做,我会得到:

x <- local2({
    a <- 2
    a * 2
}) 
x
## [1] 4
a
## [1] 2

所以 a 泄漏到全球环境中。这是为什么?

编辑: 更神秘:为什么它不会发生:

eval(quote({a <- 2; a*2}), new.env())
## [1]  4
a
## Error: object 'a' not found

R 函数的参数作为承诺传递。除非特别要求该值,否则不会对其进行评估。所以看看

# clean up first
if exists("a") rm(a)

f <- function(x) print(1)
f(a<-1)
# [1] 1
a
# Error: object 'a' not found
g <- function(x) print(x)
g(a<-1)
# [1] 1
a
# [1] 1

请注意,在 g() 函数中,我们使用传递给函数的值,该值是对 a 的赋值,以便在全局环境中创建 a。使用 f(),永远不会创建该变量,因为该函数参数仍然是承诺结束,从未被评估。

如果你想访问一个参数而不评估它,你需要使用类似 match.call()subsititute() 的东西。 local() 函数执行后者。

如果删除 eval.parent(),您会看到替换将参数中未计算的表达式放入对 eval().

的新调用中
h <- function(expr, envir = new.env()){
    substitute(eval(quote(expr), envir))
}
h(a<-1)
# eval(quote(a <- 1), new.env())

好像你在做什么

j<- function(x) {
  quote(x)
}
j(a<-1)
# x

您并没有真正创建新的函数调用。此外,当您 eval() 该表达式时,您会从其原始调用环境触发 x 的评估(触发承诺的评估),而不是在新环境中评估表达式。

local() 然后使用 eval.parent() 以便您可以在块内的环境中使用现有变量。例如

b<-5
local({
  a <- b
  a * 2
})
# [1] 10

看看这里的行为

local2 <- function(expr, envir = new.env()){
    eval(quote(expr), envir)
}
local2({a<-5; a})
# [1] 5
local2({a<-5; a}, list(a=100, expr="hello"))
# [1] "hello"

看看当我们使用非空环境时,eval() 在环境中查找 expr,而不是在环境中计算代码块。