我是R的新手,当我阅读手册时,我遇到了一个关于词汇范围的段落以及这个代码示例:
open.account <- function(total) {
list(
deposit = function(amount) {
if(amount <= 0)
stop("Deposits must be positive!\n")
total <<- total + amount
cat(amount, "deposited. Your balance is", total, "\n\n")
},
withdraw = function(amount) {
if(amount > total)
stop("You don't have that much money!\n")
total <<- total - amount
cat(amount, "withdrawn. Your balance is", total, "\n\n")
},
balance = function() {
cat("Your balance is", total, "\n\n")
}
)
}
ross <- open.account(100)
robert <- open.account(200)
ross$withdraw(30)
ross$balance()
robert$balance()
ross$deposit(50)
ross$balance()
ross$withdraw(500)
所以,我理解上面代码的作用,我想我仍然对它的工作原理感到困惑。如果在函数执行完毕后仍然可以访问函数的“局部”变量,那么在不再需要变量时是不是很难或不可能预测?在上面的代码中,如果它被用作更大程序的一部分,那么“total”是否会保存在内存中直到整个程序完成?(基本上成为一个全局变量记忆)如果这是真的,不会这导致内存使用问题?
我在这个网站上看了另外两个问题:“如何实施Lexical Scoping?”和“为什么词汇范围首选编译器?”。答案就在我脑海中,但它让我想知道:如果(正如我猜测的那样)编译器不只是使所有变量全局(内存方式)而是使用某种技术来预测某些变量何时不会不再需要并且可以被删除,不会做这项工作实际上会使编译器上的事情变得更难而不是更容易吗?
我知道这有很多不同的问题,但任何帮助都会很好,谢谢。
答案 0 :(得分:5)
在R中,每个函数[1]都有一个封闭的环境。除了那些作为参数传入的对象,或者它在代码中创建的对象之外,它还是它所知道的对象的集合。
在提示符下创建函数时,其环境是全局环境。这只是工作区中对象的集合,您可以通过键入ls()
来查看。例如,如果工作区包含数据框Df
,则可以创建如下函数:
showDfRows <- function()
{
cat("The number of rows in Df is: ", nrow(Df, "\n")
return(NULL)
}
即使您没有将其作为参数传递,您的函数也会知道Df
;它存在于funtion的环境中。环境可以嵌套,这就像包命名空间这样的工作方式。例如,即使您的工作区不包含任何名为lm(y ~ x, data=Df)
的对象,您也可以使lm
适合回归。这是因为全球环境的父母链包括stats
包,这是lm
函数所在的位置。[2]
当在另一个函数内创建函数时,它们的封闭环境是其父函数的评估框架。这意味着子函数可以访问父级已知的所有对象。例如:
f <- function(x)
{
g <- function()
{
cat("The value of x is ", x, "\n")
}
return(NULL)
}
请注意,g
不包含任何名为x
的对象,也不包含任何名为x
的对象。但是,它仍然有效,因为它将从其父x
的评估框架中检索f
。
这是上面的代码使用的技巧。运行open_account
时,它会创建一个评估框,用于执行其代码。 open_account
然后会创建3个函数deposit
,withdraw
和balance
。这3个中的每一个都具有open_account
的评估框架作为其封闭环境。在此评估框架中,有一个名为total
的变量,其值由您传入,并由deposit
,withdraw
和balance
操纵。
open_account
完成后,会返回一个列表。如果这是一个常规函数,它的评估框架现在将由R处理。但是,在这种情况下,R可以看到返回的列表包含需要使用该评估框架的函数;所以框架继续存在。
那么,为什么罗斯和罗伯特的账户不会相互冲突?每次执行open_account
时,R都会创建一个 new 评估框架。打开Ross'和Robert的帐户的框架完全是分开的,就像,如果你运行lm(y ~ x, data=Df)
,如果你运行lm(y ~ x, data=Df2
,会有一个单独的框架。每次open_account
返回时,它都会带来一个新环境,用于存储刚刚创建的余额。 (还包含deposit
,withdraw
和balance
函数的新副本,但通常我们可以忽略用于此的内存。)< / p>
[1]技术上每一次关闭,但让我们不是泥泞的事情
[2]再次说明,命名空间和环境之间存在技术上的区别,但这里并不重要
答案 1 :(得分:3)
您应该将open.account
视为生成“函数”的命名个体实例的生成器函数。每个个人帐户都有一个本地“总计”和一组按特定总计运行的功能。 (没有编译器; R被解释。)局部变量'total'会占用空间,直到删除它的对象为止。我不认为“全球”是讨论这个问题的好方法(尽管帮助页面中有语言)。如果您在命令行(即“查看”.GlobalEnv
)并执行了ls()
来电,则您将看不到任何开户帐户“总计”。
如果您想创建一个代码可检查的版本,@ G. Grothendieck在2011年在R-help中提出的策略可能会很有趣:
open.account <- function(total) {
this<-environment()
list(this,
deposit = function(amount) {
if(amount <= 0)
stop("Deposits must be positive!\n")
total <<- total + amount
cat(amount, "deposited. Your balance is", total, "\n\n")
},
withdraw = function(amount) {
if(amount > total)
stop("You don't have that much money!\n")
total <<- total - amount
cat(amount, "withdrawn. Your balance is", total, "\n\n")
},
balance = function() {
cat("Your balance is", total, "\n\n")
}
)
}
ross <- open.account(100)
ross$deposit(200)
ross[[1]]$total
[1] 300
如果您命名了第一个列表元素。 '这'你能做到:
> ross$deposit(200)
200 deposited. Your balance is 300
> ross$this$total
[1] 300
我在“词汇”这个词上遇到了一段时间的问题。我无法在最长的时间内弄清楚为什么它会被这个名字所调用。最后我来到一个字典类比,也许是不同版本的字典。在一个特定的出版时间,一个词从其定义中获得含义。如果发布新词典,其含义可能会发生变化,但研究与第一版同时发表的材料的人应该根据早期的词典版本而不是后来的词典来解释它。